[exim-cvs] Track tainted data and refuse to expand it

Startseite
Nachricht löschen
Nachricht beantworten
Autor: Exim Git Commits Mailing List
Datum:  
To: exim-cvs
Betreff: [exim-cvs] Track tainted data and refuse to expand it
Gitweb: https://git.exim.org/exim.git/commitdiff/f3ebb786e451da973560f1c9d8cdb151d25108b5
Commit:     f3ebb786e451da973560f1c9d8cdb151d25108b5
Parent:     21aa05977abff1eaa69bb97ef99080220915f7c0
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Thu Jul 25 12:06:07 2019 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Thu Jul 25 12:06:07 2019 +0100


    Track tainted data and refuse to expand it
---
 doc/doc-docbook/spec.xfpt                          |   13 +-
 doc/doc-txt/ChangeLog                              |    4 +
 src/OS/unsupported/os.c-IRIX                       |    2 +-
 src/OS/unsupported/os.c-IRIX6                      |    2 +-
 src/OS/unsupported/os.c-IRIX632                    |    2 +-
 src/OS/unsupported/os.c-IRIX65                     |    2 +-
 src/OS/unsupported/os.c-cygwin                     |    4 +-
 src/exim_monitor/em_log.c                          |   11 +-
 src/exim_monitor/em_main.c                         |    4 +-
 src/exim_monitor/em_menu.c                         |    4 +-
 src/exim_monitor/em_queue.c                        |   14 +-
 src/exim_monitor/em_strip.c                        |    2 +-
 src/exim_monitor/em_version.c                      |    6 +-
 src/exim_monitor/em_xs.c                           |    4 +-
 src/src/acl.c                                      |   46 +-
 src/src/arc.c                                      |   17 +-
 src/src/auths/call_pam.c                           |    7 +-
 src/src/auths/cram_md5.c                           |    2 +-
 src/src/auths/cyrus_sasl.c                         |    5 +-
 src/src/auths/heimdal_gssapi.c                     |   10 +-
 src/src/auths/pwcheck.c                            |    3 +-
 src/src/auths/xtextdecode.c                        |    2 +-
 src/src/auths/xtextencode.c                        |    2 +-
 src/src/base64.c                                   |    4 +-
 src/src/bmi_spam.c                                 |   13 +-
 src/src/child.c                                    |    2 +-
 src/src/daemon.c                                   |   42 +-
 src/src/dane-openssl.c                             |    2 +-
 src/src/dbfn.c                                     |   13 +-
 src/src/dcc.c                                      |   30 +-
 src/src/debug.c                                    |   17 +-
 src/src/deliver.c                                  |   56 +-
 src/src/dkim.c                                     |    6 +-
 src/src/dmarc.c                                    |    6 +-
 src/src/dns.c                                      |   21 +-
 src/src/drtables.c                                 |   17 +-
 src/src/dummies.c                                  |   12 +-
 src/src/environment.c                              |   15 +-
 src/src/exim.c                                     |  110 +-
 src/src/exim_dbmbuild.c                            |   14 +-
 src/src/exim_dbutil.c                              |   29 +-
 src/src/expand.c                                   |   99 +-
 src/src/filter.c                                   | 1123 ++++++++++----------
 src/src/filtertest.c                               |    4 +-
 src/src/functions.h                                |  136 ++-
 src/src/globals.c                                  |    2 +-
 src/src/globals.h                                  |    2 +-
 src/src/hash.c                                     |   11 +-
 src/src/header.c                                   |   13 +-
 src/src/host.c                                     |  105 +-
 src/src/ip.c                                       |    2 +-
 src/src/local_scan.h                               |    5 +-
 src/src/log.c                                      |   58 +-
 src/src/lookups/cdb.c                              |   54 +-
 src/src/lookups/dbmdb.c                            |    2 +-
 src/src/lookups/dnsdb.c                            |    2 +-
 src/src/lookups/ibase.c                            |   41 +-
 src/src/lookups/json.c                             |    6 +-
 src/src/lookups/ldap.c                             |   47 +-
 src/src/lookups/lmdb.c                             |    2 +-
 src/src/lookups/lsearch.c                          |    8 +-
 src/src/lookups/mysql.c                            |    8 +-
 src/src/lookups/nis.c                              |    2 +-
 src/src/lookups/nisplus.c                          |    4 +-
 src/src/lookups/oracle.c                           |   16 +-
 src/src/lookups/pgsql.c                            |    9 +-
 src/src/lookups/redis.c                            |    8 +-
 src/src/lookups/sqlite.c                           |    2 +-
 src/src/macros.h                                   |   13 +-
 src/src/malware.c                                  |   24 +-
 src/src/match.c                                    |   13 +-
 src/src/mime.c                                     |    4 +-
 src/src/moan.c                                     |    2 +-
 src/src/mytypes.h                                  |   44 +-
 src/src/os.c                                       |    8 +-
 src/src/parse.c                                    |   26 +-
 src/src/pdkim/pdkim.c                              |   24 +-
 src/src/pdkim/signing.c                            |    6 +-
 src/src/queue.c                                    |   16 +-
 src/src/rda.c                                      |   47 +-
 src/src/readconf.c                                 |   61 +-
 src/src/receive.c                                  |   42 +-
 src/src/regex.c                                    |    8 +-
 src/src/retry.c                                    |    7 +-
 src/src/rewrite.c                                  |   22 +-
 src/src/rfc2047.c                                  |    5 +-
 src/src/route.c                                    |   36 +-
 src/src/routers/dnslookup.c                        |    2 +-
 src/src/routers/ipliteral.c                        |    2 +-
 src/src/routers/iplookup.c                         |    4 +-
 src/src/routers/manualroute.c                      |    2 +-
 src/src/routers/redirect.c                         |  166 ++-
 src/src/routers/rf_change_domain.c                 |    2 +-
 src/src/routers/rf_get_munge_headers.c             |    4 +-
 src/src/routers/rf_get_transport.c                 |   19 +-
 src/src/routers/rf_queue_add.c                     |    2 +-
 src/src/search.c                                   |   16 +-
 src/src/setenv.c                                   |    2 +-
 src/src/sieve.c                                    |   91 +-
 src/src/smtp_in.c                                  |   98 +-
 src/src/smtp_out.c                                 |   21 +-
 src/src/spam.c                                     |    4 +-
 src/src/spool_in.c                                 |  117 +-
 src/src/spool_mbox.c                               |   11 +-
 src/src/spool_out.c                                |   47 +-
 src/src/store.c                                    |  592 ++++++++---
 src/src/store.h                                    |   52 +-
 src/src/string.c                                   |  214 ++--
 src/src/tls-gnu.c                                  |   29 +-
 src/src/tls-openssl.c                              |    4 +-
 src/src/tlscert-gnu.c                              |   22 +-
 src/src/tlscert-openssl.c                          |    8 +-
 src/src/transport.c                                |   53 +-
 src/src/transports/appendfile.c                    |   31 +-
 src/src/transports/appendfile.h                    |    2 +-
 src/src/transports/autoreply.c                     |   50 +-
 src/src/transports/lmtp.c                          |   17 +-
 src/src/transports/pipe.c                          |    4 +-
 src/src/transports/smtp.c                          |   49 +-
 src/src/transports/smtp.h                          |    3 -
 src/src/transports/tf_maildir.c                    |   38 +-
 src/src/tree.c                                     |   18 +-
 src/src/utf8.c                                     |    4 +-
 src/src/verify.c                                   |   13 +-
 src/src/version.c                                  |    6 +-
 test/confs/0102                                    |    4 +-
 test/confs/0137                                    |    6 +-
 test/confs/0140                                    |    2 +-
 test/confs/0284                                    |    4 +-
 test/confs/0386                                    |   13 +-
 test/confs/0428                                    |  100 +-
 test/confs/0478                                    |    4 +-
 test/confs/0504                                    |    2 +-
 test/confs/0610                                    |   10 +-
 test/confs/1003                                    |   43 +-
 test/confs/5000                                    |    8 +-
 test/confs/5050                                    |    4 +-
 test/confs/5103                                    |    2 +-
 test/log/0137                                      |    8 +-
 test/log/0428                                      |   18 +-
 test/log/0610                                      |    4 +-
 test/log/1003                                      |   10 +-
 test/log/5000                                      |    2 +-
 test/mail/0137.userx                               |   36 +-
 test/mail/0428.inbox.JUNK                          |   56 +-
 test/mail/0428.someone                             |    2 +-
 test/mail/0428.userx                               |   56 -
 test/mail/0428.userx-sawsuffix                     |   13 +-
 test/mail/0428.userx13                             |   17 +
 test/mail/{0428.userx-sawsuffix => 0428.userx14}   |   13 +-
 test/mail/0428.userx9                              |   20 +
 test/mail/5000.new/1.myhost.test.ex                |    1 -
 test/mail/5000.new/2.myhost.test.ex                |    1 -
 test/mail/5000.new/3.myhost.test.ex:S370           |    2 -
 test/mail/5000.new/4.myhost.test.ex,S=370          |    2 -
 test/mail/5000.new/5.myhost.test.ex                |    2 -
 test/mail/5000.new/6.myhost.test.ex                |    7 -
 test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S |    2 -
 test/msglog/5000.10HmaX-0005vi-00                  |    2 +-
 test/paniclog/1003                                 |    2 +-
 test/paniclog/5000                                 |    2 +-
 test/runtest                                       |    2 +-
 test/scripts/0000-Basic/0002                       |    3 +-
 test/scripts/0000-Basic/0137                       |   20 +-
 test/scripts/0000-Basic/0428                       |   38 +-
 test/scripts/0000-Basic/0504                       |    9 +-
 test/scripts/1000-Basic-ipv6/1003                  |   25 +-
 test/scripts/5000-maildir/5000                     |   41 +-
 test/scripts/5100-lmtp-transport/5103              |    1 -
 test/stderr/0002                                   |   13 +-
 test/stderr/0023                                   |    2 +-
 test/stderr/0037                                   |    8 +-
 test/stderr/0084                                   |    4 +-
 test/stderr/0085                                   |   12 +-
 test/stderr/0123                                   |   72 +-
 test/stderr/0279                                   |    4 +-
 test/stderr/0297                                   |    8 +-
 test/stderr/0360                                   |   16 +-
 test/stderr/0361                                   |    4 +-
 test/stderr/0364                                   |   28 +-
 test/stderr/0370                                   |    2 +-
 test/stderr/0377                                   |   48 +-
 test/stderr/0378                                   |   38 +-
 test/stderr/0379                                   |   12 +-
 test/stderr/0380                                   |    8 +-
 test/stderr/0382                                   |    8 +-
 test/stderr/0386                                   |  144 ++-
 test/stderr/0388                                   |    4 +-
 test/stderr/0393                                   |    4 +-
 test/stderr/0399                                   |   12 +-
 test/stderr/0402                                   |   20 +-
 test/stderr/0403                                   |    4 +-
 test/stderr/0404                                   |    6 +-
 test/stderr/0426                                   |    4 +-
 test/stderr/0464                                   |    8 +-
 test/stderr/0544                                   |    6 +
 test/stderr/1003                                   |    2 +-
 test/stderr/5000                                   |    2 +-
 test/stderr/5004                                   |    7 +-
 test/stderr/5204                                   |    4 +-
 test/stderr/5410                                   |   22 +
 test/stderr/5420                                   |   22 +
 test/stdout/0002                                   |    1 +
 test/stdout/0035                                   |   14 +-
 test/stdout/3415                                   |   20 +-
 205 files changed, 3291 insertions(+), 2496 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index c4d6112..32d57d0 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -9219,7 +9219,13 @@ dependent upon the option for which a value is sought; in this documentation,
options for which string expansion is performed are marked with &dagger; after
the data type. ACL rules always expand strings. A couple of expansion
conditions do not expand some of the brace-delimited branches, for security
-reasons.
+reasons,
+.new
+.cindex "tainted data" expansion
+.cindex expansion "tainted data"
+and expansion of data deriving from the sender (&"tainted data"&)
+is not permitted.
+.wen



@@ -39543,6 +39549,11 @@ was received from the client, this records the Distinguished Name from that
certificate.
.endlist

+.new
+Any of the above may have an extra hyphen prepended, to indicate the the
+corresponding data is untrusted.
+.wen
+
Following the options there is a list of those addresses to which the message
is not to be delivered. This set of addresses is initialized from the command
line when the &%-t%& option is used and &%extract_addresses_remove_arguments%&
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 2e83903..78cb127 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -149,6 +149,10 @@ JH/30 Bug 2411: Fix DSN generation when RFC 3461 failure notification is

JH/31 Avoid re-expansion in ${sort } expansion. (CVE-2019-13917)

+JH/32 Introduce a general tainting mechanism for values read from the input
+      channel, and values derived from them.  Refuse to expand any tainted
+      values, to catch one form of exploit.
+


 Exim version 4.92
 -----------------
diff --git a/src/OS/unsupported/os.c-IRIX b/src/OS/unsupported/os.c-IRIX
index 1f6b0e1..c1539cb 100644
--- a/src/OS/unsupported/os.c-IRIX
+++ b/src/OS/unsupported/os.c-IRIX
@@ -59,7 +59,7 @@ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s",
     strerror(errno));


-buf = store_get(needed);
+buf = store_get(needed, FALSE);

 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s",
diff --git a/src/OS/unsupported/os.c-IRIX6 b/src/OS/unsupported/os.c-IRIX6
index 1f6b0e1..c1539cb 100644
--- a/src/OS/unsupported/os.c-IRIX6
+++ b/src/OS/unsupported/os.c-IRIX6
@@ -59,7 +59,7 @@ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s",
     strerror(errno));


-buf = store_get(needed);
+buf = store_get(needed, FALSE);

 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s",
diff --git a/src/OS/unsupported/os.c-IRIX632 b/src/OS/unsupported/os.c-IRIX632
index 1f6b0e1..c1539cb 100644
--- a/src/OS/unsupported/os.c-IRIX632
+++ b/src/OS/unsupported/os.c-IRIX632
@@ -59,7 +59,7 @@ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s",
     strerror(errno));


-buf = store_get(needed);
+buf = store_get(needed, FALSE);

 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s",
diff --git a/src/OS/unsupported/os.c-IRIX65 b/src/OS/unsupported/os.c-IRIX65
index 1f6b0e1..c1539cb 100644
--- a/src/OS/unsupported/os.c-IRIX65
+++ b/src/OS/unsupported/os.c-IRIX65
@@ -59,7 +59,7 @@ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s",
     strerror(errno));


-buf = store_get(needed);
+buf = store_get(needed, FALSE);

 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
   log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s",
diff --git a/src/OS/unsupported/os.c-cygwin b/src/OS/unsupported/os.c-cygwin
index c9464aa..5ca05a8 100644
--- a/src/OS/unsupported/os.c-cygwin
+++ b/src/OS/unsupported/os.c-cygwin
@@ -167,10 +167,10 @@ void cygwin_premain2(int argc, char ** argv, struct per_process * ptr)
         cygwin_debug = TRUE;
         fprintf(stderr, "CYGWIN = \"%s\".\n", cygenv);
         if (((size = cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, 0)) > 0)
-      && ((win32_path = malloc(size)) != NULL)
+      && ((win32_path = store_malloc(size)) != NULL)
          && (cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, size) == 0)) {
             fprintf(stderr, " Root / mapped to %ls.\n", win32_path);
-        free(win32_path);
+        store_free(win32_path);
     }
       }
       else if (argv[i][1] == 'b' && argv[i][2] == 'd') {
diff --git a/src/exim_monitor/em_log.c b/src/exim_monitor/em_log.c
index 52eef6b..1e1dc7c 100644
--- a/src/exim_monitor/em_log.c
+++ b/src/exim_monitor/em_log.c
@@ -227,7 +227,7 @@ if (LOG != NULL)
     {
     uschar *id;
     uschar *p = buffer;
-    void *reset_point;
+    rmark reset_point;
     int length = Ustrlen(buffer);
     int i;


@@ -240,7 +240,7 @@ if (LOG != NULL)
     it for various regular expression matches and take appropriate
     action. Get the current store point so we can reset to it. */


-    reset_point = store_get(0);
+    reset_point = store_mark();


     /* First, update any stripchart data values, noting that the zeroth
     stripchart is the queue length, which is handled elsewhere, and the
@@ -364,9 +364,10 @@ link count of zero on the currently open file. */
 if (log_datestamping)
   {
   uschar log_file_wanted[256];
-  /* Do *not* use "%s" here, we need the %D datestamp in the log_file to
-   *   be expanded! */
-  string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
+  /* Do *not* use "%s" here, we need the %D datestamp in the log_file string to
+  be expanded.  The trailing NULL arg is to quieten preprocessors that need at
+  least one arg for a variadic set in a macro. */
+  string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file, NULL);
   if (Ustrcmp(log_file_wanted, log_file_open) != 0)
     {
     if (LOG != NULL)
diff --git a/src/exim_monitor/em_main.c b/src/exim_monitor/em_main.c
index 7aa760e..9c7f442 100644
--- a/src/exim_monitor/em_main.c
+++ b/src/exim_monitor/em_main.c
@@ -613,7 +613,7 @@ message_subdir[1] = 0;
 constructing file names and things. This call will initialize
 the store_get() function. */


-big_buffer = store_get(big_buffer_size);
+big_buffer = store_get(big_buffer_size, FALSE);

/* Set up the version string and date and output them */

@@ -655,7 +655,7 @@ if (log_file[0] != 0)
{
/* Do *not* use "%s" here, we need the %D datestamp in the log_file to
be expanded! */
- (void)string_format(log_file_open, sizeof(log_file_open), CS log_file);
+ (void)string_format(log_file_open, sizeof(log_file_open), CS log_file, NULL);
log_datestamping = string_datestamp_offset >= 0;

LOG = fopen(CS log_file_open, "r");
diff --git a/src/exim_monitor/em_menu.c b/src/exim_monitor/em_menu.c
index 31ce1a3..92e0b35 100644
--- a/src/exim_monitor/em_menu.c
+++ b/src/exim_monitor/em_menu.c
@@ -651,7 +651,7 @@ static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
uschar buffer[256];
header_line *h, *next;
Widget text = text_create(US client_data, text_depth);
-void *reset_point;
+rmark reset_point;

 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
@@ -659,7 +659,7 @@ call_data = call_data;
 /* Remember the point in the dynamic store so we can recover to it afterwards.
 Then use Exim's function to read the header. */


-reset_point = store_get(0);
+reset_point = store_mark();

sprintf(CS buffer, "%s-H", US client_data);
if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
diff --git a/src/exim_monitor/em_queue.c b/src/exim_monitor/em_queue.c
index c8d9a40..f121527 100644
--- a/src/exim_monitor/em_queue.c
+++ b/src/exim_monitor/em_queue.c
@@ -138,7 +138,7 @@ acl_var_create(uschar *name)
{
tree_node *node, **root;
root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
-node = store_get(sizeof(tree_node) + Ustrlen(name));
+node = store_get(sizeof(tree_node) + Ustrlen(name), FALSE);
Ustrcpy(node->name, name);
node->data.ptr = NULL;
(void)tree_insertnode(root, node);
@@ -156,7 +156,7 @@ set_up(uschar *name, int dir_char)
{
int i, rc, save_errno;
struct stat statdata;
-void *reset_point;
+rmark reset_point;
uschar *p;
queue_item *q = (queue_item *)store_malloc(sizeof(queue_item));
uschar buffer[256];
@@ -182,7 +182,7 @@ Before reading the header remember the position in the dynamic store so that
we can recover the store into which the header is read. All data read by
spool_read_header that is to be preserved is copied into malloc store. */

-reset_point = store_get(0);
+reset_point = store_mark();
 message_size = 0;
 message_subdir[0] = dir_char;
 sprintf(CS buffer, "%s-H", name);
@@ -224,9 +224,9 @@ if (rc != spool_read_OK)
     if (Ustat(big_buffer, &statbuf) == 0)
       msg = string_sprintf("*** Format error in spool file: size = %d ***",
         statbuf.st_size);
-    else msg = string_sprintf("*** Format error in spool file ***");
+    else msg = US"*** Format error in spool file ***";
     }
-  else msg = string_sprintf("*** Cannot read spool file ***");
+  else msg = US"*** Cannot read spool file ***";


   if (rc == spool_read_hdrerror)
     {
@@ -614,7 +614,7 @@ static void update_recipients(queue_item *p)
 {
 int i;
 FILE *jread;
-void *reset_point;
+rmark reset_point;
 struct stat statdata;
 uschar buffer[1024];


@@ -634,7 +634,7 @@ if (!(jread = fopen(CS buffer, "r")))
/* Get the contents of the header file; if any problem, just give up.
Arrange to recover the dynamic store afterwards. */

-reset_point = store_get(0);
+reset_point = store_mark();
 sprintf(CS buffer, "%s-H", p->name);
 if (spool_read_header(buffer, FALSE, TRUE) != spool_read_OK)
   {
diff --git a/src/exim_monitor/em_strip.c b/src/exim_monitor/em_strip.c
index 2a5f0b8..03864d2 100644
--- a/src/exim_monitor/em_strip.c
+++ b/src/exim_monitor/em_strip.c
@@ -141,7 +141,7 @@ while (thresholds[i] > 0)
         thresh : stripchart_midmax[num];
       if (newmax == 10) sprintf(CS buffer, "%s", stripchart_name[num]);
         else sprintf(CS buffer, "%s x%d", stripchart_name[num], newmax/10);
-      if (size_stripchart != NULL && num == 1) Ustrcat(buffer, "%");
+      if (size_stripchart != NULL && num == 1) Ustrcat(buffer, US"%");
       xs_SetValues(stripchart_label[num], 1, "label", buffer);
       oldmax = stripchart_max[num];
       stripchart_max[num] = newmax;
diff --git a/src/exim_monitor/em_version.c b/src/exim_monitor/em_version.c
index e5a4ebb..52c55a4 100644
--- a/src/exim_monitor/em_version.c
+++ b/src/exim_monitor/em_version.c
@@ -34,7 +34,7 @@ version_date[0] = 0;
 Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, 31);


#else
-Ustrcpy(today, __DATE__);
+Ustrcpy(today, US __DATE__);
if (today[4] == ' ') i = 1;
today[3] = today[6] = '-';

@@ -43,8 +43,8 @@ version_date[0] = 0;
Ustrncat(version_date, today+4+i, 3-i);
Ustrncat(version_date, today, 4);
Ustrncat(version_date, today+7, 4);
-Ustrcat(version_date, " ");
-Ustrcat(version_date, __TIME__);
+Ustrcat(version_date, US" ");
+Ustrcat(version_date, US __TIME__);
#endif
}

diff --git a/src/exim_monitor/em_xs.c b/src/exim_monitor/em_xs.c
index b145fb9..ee91f7c 100644
--- a/src/exim_monitor/em_xs.c
+++ b/src/exim_monitor/em_xs.c
@@ -30,7 +30,7 @@ void xs_SetValues(Widget w, Cardinal num_args, ...)
{
int i;
va_list ap;
-Arg *aa = (num_args > 15)? (Arg *)malloc(num_args*sizeof(Arg)) : xs_temparg;
+Arg *aa = (num_args > 15)? store_malloc(num_args*sizeof(Arg)) : xs_temparg;
va_start(ap, num_args);
for (i = 0; i < num_args; i++)
{
@@ -39,7 +39,7 @@ for (i = 0; i < num_args; i++)
}
va_end(ap);
XtSetValues(w, aa, num_args);
-if (num_args > 15) free(aa);
+if (num_args > 15) store_free(aa);
}

 /* End of em_xs.c */
diff --git a/src/src/acl.c b/src/src/acl.c
index 3788612..dac2ba5 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -777,7 +777,7 @@ while ((s = (*func)()) != NULL)
       *error = string_sprintf("malformed ACL line \"%s\"", saveline);
       return NULL;
       }
-    this = store_get(sizeof(acl_block));
+    this = store_get(sizeof(acl_block), FALSE);
     *lastp = this;
     lastp = &(this->next);
     this->next = NULL;
@@ -824,7 +824,7 @@ while ((s = (*func)()) != NULL)
     return NULL;
     }


- cond = store_get(sizeof(acl_condition_block));
+ cond = store_get(sizeof(acl_condition_block), FALSE);
cond->next = NULL;
cond->type = c;
cond->u.negated = negated;
@@ -1022,7 +1022,9 @@ for (p = q; *p; p = q)

   if (!*hptr)
     {
-    header_line *h = store_get(sizeof(header_line));
+    /* The header_line struct itself is not tainted, though it points to
+    tainted data. */
+    header_line *h = store_get(sizeof(header_line), FALSE);
     h->text = hdr;
     h->next = NULL;
     h->type = newtype;
@@ -1355,7 +1357,7 @@ we return from this function. */
 t = tree_search(csa_cache, domain);
 if (t != NULL) return t->data.val;


-t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain));
Ustrcpy(t->name, domain);
(void)tree_insertnode(&csa_cache, t);

@@ -1726,7 +1728,7 @@ switch(vp->value)

     if ((rc = verify_check_notblind(case_sensitive)) != OK)
       {
-      *log_msgptr = string_sprintf("bcc recipient detected");
+      *log_msgptr = US"bcc recipient detected";
       if (smtp_return_error_details)
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
       }
@@ -2168,7 +2170,7 @@ gstring * g =
   string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");


va_start(ap, format);
-g = string_vformat(g, TRUE, format, ap);
+g = string_vformat(g, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
va_end(ap);

 gstring_release_unused(g);
@@ -2455,7 +2457,7 @@ if (!dbdb)
     /* No Bloom filter. This basic ratelimit block is initialized below. */
     HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
     dbdb_size = sizeof(*dbd);
-    dbdb = store_get(dbdb_size);
+    dbdb = store_get(dbdb_size, FALSE);        /* not tainted */
     }
   else
     {
@@ -2469,7 +2471,7 @@ if (!dbdb)
     extra = (int)limit * 2 - sizeof(dbdb->bloom);
     if (extra < 0) extra = 0;
     dbdb_size = sizeof(*dbdb) + extra;
-    dbdb = store_get(dbdb_size);
+    dbdb = store_get(dbdb_size, FALSE);        /* not tainted */
     dbdb->bloom_epoch = tv.tv_sec;
     dbdb->bloom_size = sizeof(dbdb->bloom) + extra;
     memset(dbdb->bloom, 0, dbdb->bloom_size);
@@ -2686,9 +2688,10 @@ else


dbfn_close(dbm);

-/* Store the result in the tree for future reference. */
+/* Store the result in the tree for future reference. Take the taint status
+from the key for consistency even though it's unlikely we'll ever expand this. */

-t = store_get(sizeof(tree_node) + Ustrlen(key));
+t = store_get(sizeof(tree_node) + Ustrlen(key), is_tainted(key));
t->data.ptr = dbd;
Ustrcpy(t->name, key);
(void)tree_insertnode(anchor, t);
@@ -2761,7 +2764,7 @@ if (*portend != '\0')
}

 /* Make a single-item host list. */
-h = store_get(sizeof(host_item));
+h = store_get(sizeof(host_item), FALSE);
 memset(h, 0, sizeof(host_item));
 h->name = hostname;
 h->port = portnum;
@@ -3493,7 +3496,7 @@ for (; cb; cb = cb->next)
       (sender_host_address == NULL)? US"" : sender_host_address,
       CUSS &host_data);
     if (rc == DEFER) *log_msgptr = search_error_message;
-    if (host_data) host_data = string_copy_malloc(host_data);
+    if (host_data) host_data = string_copy_perm(host_data, TRUE);
     break;


     case ACLC_LOCAL_PARTS:
@@ -3595,7 +3598,7 @@ for (; cb; cb = cb->next)
           "Directory separator not permitted in queue name: '%s'", arg);
       return ERROR;
       }
-    queue_name = string_copy_malloc(arg);
+    queue_name = string_copy_perm(arg, FALSE);
     break;


     case ACLC_RATELIMIT:
@@ -3995,13 +3998,20 @@ if (Ustrchr(ss, ' ') == NULL)
   else if (*ss == '/')
     {
     struct stat statbuf;
+    if (is_tainted(ss))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+    "attempt to open tainted ACL file name \"%s\"", ss);
+      /* Avoid leaking info to an attacker */
+      *log_msgptr = US"internal configuration error";
+      return ERROR;
+      }
     if ((fd = Uopen(ss, O_RDONLY, 0)) < 0)
       {
       *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
         strerror(errno));
       return ERROR;
       }
-
     if (fstat(fd, &statbuf) != 0)
       {
       *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
@@ -4009,7 +4019,8 @@ if (Ustrchr(ss, ' ') == NULL)
       return ERROR;
       }


-    acl_text = store_get(statbuf.st_size + 1);
+    /* If the string being used as a filename is tainted, so is the file content */
+    acl_text = store_get(statbuf.st_size + 1, is_tainted(ss));
     acl_text_end = acl_text + statbuf.st_size + 1;


     if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
@@ -4039,7 +4050,7 @@ if (!acl)
   if (!acl && *log_msgptr) return ERROR;
   if (fd >= 0)
     {
-    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss));
+    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), is_tainted(ss));
     Ustrcpy(t->name, ss);
     t->data.ptr = acl;
     (void)tree_insertnode(&acl_anchor, t);
@@ -4520,7 +4531,7 @@ acl_var_create(uschar * name)
 tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
 if (!(node = tree_search(*root, name)))
   {
-  node = store_get(sizeof(tree_node) + Ustrlen(name));
+  node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name));
   Ustrcpy(node->name, name);
   (void)tree_insertnode(root, node);
   }
@@ -4554,6 +4565,7 @@ void
 acl_var_write(uschar *name, uschar *value, void *ctx)
 {
 FILE *f = (FILE *)ctx;
+if (is_tainted(value)) putc('-', f);
 fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }


diff --git a/src/src/arc.c b/src/src/arc.c
index 7bdf4bf..c266849 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -143,7 +143,7 @@ for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
}

DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
-*pas = as = store_get(sizeof(arc_set));
+*pas = as = store_get(sizeof(arc_set), FALSE);
memset(as, 0, sizeof(arc_set));
as->next = next;
as->prev = prev;
@@ -201,7 +201,7 @@ al->complete = h;

 if (!instance_only)
   {
-  al->rawsig_no_b_val.data = store_get(h->slen + 1);
+  al->rawsig_no_b_val.data = store_get(h->slen + 1, TRUE);    /* tainted */
   memcpy(al->rawsig_no_b_val.data, h->text, off);    /* copy the header name blind */
   r = al->rawsig_no_b_val.data + off;
   al->rawsig_no_b_val.len = off;
@@ -387,7 +387,7 @@ arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
 {
 unsigned i;
 arc_set * as;
-arc_line * al = store_get(sizeof(arc_line)), ** alp;
+arc_line * al = store_get(sizeof(arc_line), FALSE), ** alp;
 uschar * e;


memset(al, 0, sizeof(arc_line));
@@ -499,7 +499,7 @@ const uschar * e;
DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
for (h = header_list; h; h = h->next)
{
- r = store_get(sizeof(hdr_rlist));
+ r = store_get(sizeof(hdr_rlist), FALSE);
r->prev = rprev;
r->used = FALSE;
r->h = h;
@@ -1100,7 +1100,7 @@ out:
static hdr_rlist *
arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
{
-hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
+hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), FALSE);
header_line * h = r->h = (header_line *)(r+1);

r->prev = list;
@@ -1191,7 +1191,8 @@ arc_sign_append_aar(gstring * g, arc_ctx * ctx,
const uschar * identity, int instance, blob * ar)
{
int aar_off = g ? g->ptr : 0;
-arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
+arc_set * as =
+ store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), FALSE);
arc_line * al = (arc_line *)(as+1);
header_line * h = (header_line *)(al+1);

@@ -1301,7 +1302,7 @@ int col;
 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);    /*XXX hardwired */
 blob sig;
 int ams_off;
-arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE);
 header_line * h = (header_line *)(al+1);


/* debug_printf("%s\n", __FUNCTION__); */
@@ -1417,7 +1418,7 @@ arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
gstring * arcset;
arc_set * as;
uschar * status = arc_ar_cv_status(ar);
-arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE);
header_line * h = (header_line *)(al+1);

gstring * hdata = NULL;
diff --git a/src/src/auths/call_pam.c b/src/src/auths/call_pam.c
index 204f449..9a46b6b 100644
--- a/src/src/auths/call_pam.c
+++ b/src/src/auths/call_pam.c
@@ -71,9 +71,7 @@ struct pam_response *reply;

if (pam_arg_ended) return PAM_CONV_ERR;

-reply = malloc(sizeof(struct pam_response) * num_msg);
-
-if (reply == NULL) return PAM_CONV_ERR;
+reply = store_get(sizeof(struct pam_response) * num_msg, FALSE);

 for (int i = 0; i < num_msg; i++)
   {
@@ -88,7 +86,7 @@ for (int i = 0; i < num_msg; i++)
       arg = US"";
       pam_arg_ended = TRUE;
       }
-    reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */
+    reply[i].resp = CS string_copy_perm(arg, FALSE); /* PAM frees resp */
     reply[i].resp_retcode = PAM_SUCCESS;
     break;


@@ -99,7 +97,6 @@ for (int i = 0; i < num_msg; i++)
     break;


     default:  /* Must be an error of some sort... */
-    free (reply);
     pam_conv_had_error = TRUE;
     return PAM_CONV_ERR;
     }
diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c
index b1c5610..a60db56 100644
--- a/src/src/auths/cram_md5.c
+++ b/src/src/auths/cram_md5.c
@@ -226,7 +226,7 @@ HDEBUG(D_auth)
   debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]);
   debug_printf("          challenge = %s\n", challenge);
   debug_printf("          received  = %s\n", clear);
-  Ustrcpy(buff,"          digest    = ");
+  Ustrcpy(buff, US"          digest    = ");
   for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
   debug_printf("%.54s\n", buff);
   }
diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c
index bce4202..480010b 100644
--- a/src/src/auths/cyrus_sasl.c
+++ b/src/src/auths/cyrus_sasl.c
@@ -114,7 +114,8 @@ auth_cyrus_sasl_options_block *ob =
 const uschar *list, *listptr, *buffer;
 int rc, i;
 unsigned int len;
-uschar *rs_point, *expanded_hostname;
+rmark rs_point;
+uschar *expanded_hostname;
 char *realm_expanded;


sasl_conn_t *conn;
@@ -175,7 +176,7 @@ HDEBUG(D_auth)
* the hierarchy is stored for us behind our back. This point
* creates a hierarchy point for this function.
*/
-rs_point = store_get(0);
+rs_point = store_mark();

 /* loop until either we get to the end of the list, or we match the
  * public name of this authenticator
diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c
index a70bc8a..273d4f4 100644
--- a/src/src/auths/heimdal_gssapi.c
+++ b/src/src/auths/heimdal_gssapi.c
@@ -96,7 +96,7 @@ void auth_heimdal_gssapi_version_report(FILE *f) {}
 static void
   exim_heimdal_error_debug(const char *, krb5_context, krb5_error_code);
 static int
-  exim_gssapi_error_defer(uschar *, OM_uint32, OM_uint32, const char *, ...)
+  exim_gssapi_error_defer(rmark, OM_uint32, OM_uint32, const char *, ...)
     PRINTF_FUNCTION(4, 5);


#define EmptyBuf(buf) do { buf.value = NULL; buf.length = 0; } while (0)
@@ -255,12 +255,12 @@ uschar *tmp1, *tmp2, *from_client;
auth_heimdal_gssapi_options_block *ob =
(auth_heimdal_gssapi_options_block *)(ablock->options_block);
BOOL handled_empty_ir;
-uschar *store_reset_point;
+rmark store_reset_point;
uschar *keytab;
uschar sasl_config[4];
uschar requested_qop;

-store_reset_point = store_get(0);
+store_reset_point = store_mark();

HDEBUG(D_auth)
debug_printf("heimdal: initialising auth context for %s\n", ablock->name);
@@ -558,7 +558,7 @@ return auth_check_serv_cond(ablock);


 static int
-exim_gssapi_error_defer(uschar *store_reset_point,
+exim_gssapi_error_defer(rmark store_reset_point,
     OM_uint32 major, OM_uint32 minor,
     const char *format, ...)
 {
@@ -571,7 +571,7 @@ gstring * g;
 HDEBUG(D_auth)
   {
   va_start(ap, format);
-  g = string_vformat(NULL, TRUE, format, ap);
+  g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
   va_end(ap);
   }


diff --git a/src/src/auths/pwcheck.c b/src/src/auths/pwcheck.c
index 54ba80f..f733b98 100644
--- a/src/src/auths/pwcheck.c
+++ b/src/src/auths/pwcheck.c
@@ -296,7 +296,8 @@ static int read_string(int fd, uschar **retval) {
         if (count > MAX_REQ_LEN) {
             return -1;
         } else {
-            *retval = store_get(count + 1);
+        /* Assume the file is trusted, so no tainting */
+            *retval = store_get(count + 1, FALSE);
             rc = (retry_read(fd, *retval, count) < (int) count);
             (*retval)[count] = '\0';
             return count;
diff --git a/src/src/auths/xtextdecode.c b/src/src/auths/xtextdecode.c
index 5312acf..95cf5da 100644
--- a/src/src/auths/xtextdecode.c
+++ b/src/src/auths/xtextdecode.c
@@ -33,7 +33,7 @@ int
 auth_xtextdecode(uschar *code, uschar **ptr)
 {
 register int x;
-uschar *result = store_get(Ustrlen(code) + 1);
+uschar *result = store_get(Ustrlen(code) + 1, is_tainted(code));
 *ptr = result;


while ((x = (*code++)) != 0)
diff --git a/src/src/auths/xtextencode.c b/src/src/auths/xtextencode.c
index 2c00c4a..30ff8f1 100644
--- a/src/src/auths/xtextencode.c
+++ b/src/src/auths/xtextencode.c
@@ -40,7 +40,7 @@ in order to get the right amount of store. */
while (c -- > 0)
count += ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')? 3 : 1;

-pp = code = store_get(count);
+pp = code = store_get(count, is_tainted(clear));

p = US clear;
c = len;
diff --git a/src/src/base64.c b/src/src/base64.c
index 289383b..6c81914 100644
--- a/src/src/base64.c
+++ b/src/src/base64.c
@@ -158,7 +158,7 @@ uschar *result;

{
int l = Ustrlen(code);
- *ptr = result = store_get(1 + l/4 * 3 + l%4);
+ *ptr = result = store_get(1 + l/4 * 3 + l%4, is_tainted(code));
}

/* Each cycle of the loop handles a quantum of 4 input bytes. For the last
@@ -244,7 +244,7 @@ static uschar *enc64table =
uschar *
b64encode(const uschar * clear, int len)
{
-uschar *code = store_get(4*((len+2)/3) + 1);
+uschar *code = store_get(4*((len+2)/3) + 1, is_tainted(clear));
uschar *p = code;

 while (len-- >0)
diff --git a/src/src/bmi_spam.c b/src/src/bmi_spam.c
index 546ac1e..6651de5 100644
--- a/src/src/bmi_spam.c
+++ b/src/src/bmi_spam.c
@@ -190,8 +190,10 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
     return NULL;
   };


- /* get store for the verdict string */
- verdicts = store_get(1);
+ /* Get store for the verdict string. Since we are processing message data, assume that
+ the verdict is tainted. XXX this should use a growable-string */
+
+ verdicts = store_get(1, TRUE);
*verdicts = '\0';

   for ( err = bmiAccessFirstVerdict(message, &verdict);
@@ -200,7 +202,8 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
     char *verdict_str;


     err = bmiCreateStrFromVerdict(verdict,&verdict_str);
-    if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
+    if (!store_extend(verdicts, TRUE,
+      Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
       /* can't allocate more store */
       return NULL;
     };
@@ -299,7 +302,7 @@ uschar *bmi_get_alt_location(uschar *base64_verdict) {
   }
   else {
     /* deliver to alternate location */
-    rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1);
+    rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, TRUE);
     Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
     rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
   };
@@ -324,7 +327,7 @@ uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
     return NULL;


/* allocate room for the b64 verdict string */
- verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1);
+ verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, TRUE);

/* loop through verdicts */
verdict_ptr = bmi_verdicts;
diff --git a/src/src/child.c b/src/src/child.c
index e53e448..d3cd882 100644
--- a/src/src/child.c
+++ b/src/src/child.c
@@ -75,7 +75,7 @@ int n = 0;
int extra = pcount ? *pcount : 0;
uschar **argv;

-argv = store_get((extra + acount + MAX_CLMACROS + 18) * sizeof(char *));
+argv = store_get((extra + acount + MAX_CLMACROS + 18) * sizeof(char *), FALSE);

/* In all case, the list starts out with the path, any macros, and a changed
config file. */
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 0b4d347..21ce2f0 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -146,7 +146,7 @@ int max_for_this_host = 0;
int save_log_selector = *log_selector;
gstring * whofrom;

-void *reset_point = store_get(0);
+rmark reset_point = store_mark();

 /* Make the address available in ASCII representation, and also fish out
 the remote port. */
@@ -395,7 +395,7 @@ if (pid == 0)
           "please try again later.\r\n", FALSE);
         mac_smtp_fflush();
         search_tidyup();
-        _exit(EXIT_FAILURE);
+        exim_underbar_exit(EXIT_FAILURE);
         }
       }
     else if (*nah) smtp_active_hostname = nah;
@@ -481,14 +481,14 @@ if (pid == 0)
     {
     mac_smtp_fflush();
     search_tidyup();
-    _exit(EXIT_SUCCESS);
+    exim_underbar_exit(EXIT_SUCCESS);
     }


   for (;;)
     {
     int rc;
     message_id[0] = 0;            /* Clear out any previous message_id */
-    reset_point = store_get(0);   /* Save current store high water point */
+    reset_point = store_mark();   /* Save current store high water point */


     DEBUG(D_any)
       debug_printf("Process %d is ready for new message\n", (int)getpid());
@@ -509,7 +509,7 @@ if (pid == 0)
     cancel_cutthrough_connection(TRUE, US"receive dropped");
         mac_smtp_fflush();
         smtp_log_no_mail();               /* Log no mail if configured */
-        _exit(EXIT_SUCCESS);
+        exim_underbar_exit(EXIT_SUCCESS);
         }
       if (message_id[0] == 0) continue;   /* No message was accepted */
       }
@@ -532,7 +532,7 @@ if (pid == 0)
       /*XXX should we pause briefly, hoping that the client will be the
       active TCP closer hence get the TCP_WAIT endpoint? */
       DEBUG(D_receive) debug_printf("SMTP>>(close on process exit)\n");
-      _exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
+      exim_underbar_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
       }


     /* Show the recipients when debugging */
@@ -565,6 +565,7 @@ if (pid == 0)
       int r = receive_messagecount;
       BOOL q = f.queue_only_policy;
       smtp_reset(reset_point);
+      reset_point = NULL;
       f.queue_only_policy = q;
       receive_messagecount = r;
       }
@@ -665,7 +666,7 @@ if (pid == 0)


         (void) deliver_message(message_id, FALSE, FALSE);
         search_tidyup();
-        _exit(EXIT_SUCCESS);
+        exim_underbar_exit(EXIT_SUCCESS);
         }


       if (dpid > 0)
@@ -696,13 +697,14 @@ else
     if (smtp_slots[i].pid <= 0)
       {
       smtp_slots[i].pid = pid;
-      if (smtp_accept_max_per_host != NULL)
+      /* Connection closes come asyncronously, so we cannot stack this store */
+      if (smtp_accept_max_per_host)
         smtp_slots[i].host_address = string_copy_malloc(sender_host_address);
       smtp_accept_count++;
       break;
       }
   DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
-    smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
+    smtp_accept_count, smtp_accept_count == 1 ? "" : "es");
   }


/* Get here via goto in error cases */
@@ -833,7 +835,6 @@ pid_t pid;

 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   {
-  int i;
   DEBUG(D_any)
     {
     debug_printf("child %d ended: status=0x%x\n", (int)pid, status);
@@ -851,6 +852,7 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)


   if (smtp_slots)
     {
+    int i;
     for (i = 0; i < smtp_accept_max; i++)
       if (smtp_slots[i].pid == pid)
         {
@@ -871,7 +873,7 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   if (queue_pid_slots)
     {
     int max = atoi(CS expand_string(queue_run_max));
-    for (i = 0; i < max; i++)
+    for (int i = 0; i < max; i++)
       if (queue_pid_slots[i] == pid)
         {
         queue_pid_slots[i] = 0;
@@ -927,7 +929,7 @@ DEBUG(D_any|D_v) debug_selector |= D_pid;
 if (f.inetd_wait_mode)
   {
   listen_socket_count = 1;
-  listen_sockets = store_get(sizeof(int));
+  listen_sockets = store_get(sizeof(int), FALSE);
   (void) close(3);
   if (dup2(0, 3) == -1)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
@@ -1115,7 +1117,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
   sep = 0;
   while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
     pct++;
-  default_smtp_port = store_get((pct+1) * sizeof(int));
+  default_smtp_port = store_get((pct+1) * sizeof(int), FALSE);
   list = daemon_smtp_port;
   sep = 0;
   for (pct = 0;
@@ -1203,7 +1205,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     ipa->port = default_smtp_port[0];
     for (int i = 1; default_smtp_port[i] > 0; i++)
       {
-      ip_address_item *new = store_get(sizeof(ip_address_item));
+      ip_address_item *new = store_get(sizeof(ip_address_item), FALSE);


       memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
       new->port = default_smtp_port[i];
@@ -1261,7 +1263,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)


   for (ipa = addresses; ipa; ipa = ipa->next)
     listen_socket_count++;
-  listen_sockets = store_get(sizeof(int) * listen_socket_count);
+  listen_sockets = store_get(sizeof(int) * listen_socket_count, FALSE);


} /* daemon_listen but not inetd_wait_mode */

@@ -1284,7 +1286,7 @@ if (f.daemon_listen)

   if (smtp_accept_max > 0)
     {
-    smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot));
+    smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot), FALSE);
     for (int i = 0; i < smtp_accept_max; i++) smtp_slots[i] = empty_smtp_slot;
     }
   }
@@ -1572,15 +1574,15 @@ coming from Exim, not whoever started the daemon. */


originator_uid = exim_uid;
originator_gid = exim_gid;
-originator_login = ((pw = getpwuid(exim_uid)) != NULL)?
- string_copy_malloc(US pw->pw_name) : US"exim";
+originator_login = (pw = getpwuid(exim_uid))
+ ? string_copy_perm(US pw->pw_name, FALSE) : US"exim";

/* Get somewhere to keep the list of queue-runner pids if we are keeping track
of them (and also if we are doing queue runs). */

if (queue_interval > 0 && local_queue_run_max > 0)
{
- queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t));
+ queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t), FALSE);
for (int i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0;
}

@@ -1895,7 +1897,7 @@ for (;;)
           /* No need to re-exec; SIGALRM remains set to the default handler */


           queue_run(NULL, NULL, FALSE);
-          _exit(EXIT_SUCCESS);
+          exim_underbar_exit(EXIT_SUCCESS);
           }


         if (pid < 0)
diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c
index c0bbf3d..9b86a48 100644
--- a/src/src/dane-openssl.c
+++ b/src/src/dane-openssl.c
@@ -129,7 +129,7 @@ static ERR_STRING_DATA dane_str_reasons[] = {
 };
 #endif


-#define DANEerr(f, r) ERR_PUT_error(err_lib_dane, (f), (r), __FILE__, __LINE__)
+#define DANEerr(f, r) ERR_PUT_error(err_lib_dane, (f), (r), __FUNCTION__, __LINE__)

 static int err_lib_dane = -1;
 static int dane_idx = -1;
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index a607756..63a1aef 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -206,7 +206,7 @@ if (created && geteuid() == root_uid)
     if (Ustrncmp(ent->d_name, name, namelen) == 0)
       {
       struct stat statbuf;
-      Ustrcpy(lastname, ent->d_name);
+      Ustrcpy(lastname, US ent->d_name);
       if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
         {
         DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
@@ -303,7 +303,7 @@ dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
 void *yield;
 EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));


memcpy(key_copy, key, klen);

@@ -316,7 +316,10 @@ EXIM_DATUM_SIZE(key_datum) = klen;

if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;

-yield = store_get(EXIM_DATUM_SIZE(result_datum));
+/* Assume the data store could have been tainted. Properly, we should
+store the taint status with the data. */
+
+yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);

@@ -347,7 +350,7 @@ dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
EXIM_DATUM key_datum, value_datum;
dbdata_generic *gptr = (dbdata_generic *)ptr;
int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));

memcpy(key_copy, key, klen);
gptr->time_stamp = time(NULL);
@@ -381,7 +384,7 @@ int
dbfn_delete(open_db *dbblock, const uschar *key)
{
int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));

DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);

diff --git a/src/src/dcc.c b/src/src/dcc.c
index 5aa2b17..4c9c096 100644
--- a/src/src/dcc.c
+++ b/src/src/dcc.c
@@ -156,11 +156,11 @@ dcc_process(uschar **listptr)
       debug_printf("DCC: Client IP (default): %s\n", client_ip);
   }
   /* strncat(opts, my_request, strlen(my_request)); */
-  Ustrcat(opts, "\n");
+  Ustrcat(opts, US"\n");
   Ustrncat(opts, client_ip, sizeof(opts)-Ustrlen(opts)-1);
-  Ustrncat(opts, "\nHELO ", sizeof(opts)-Ustrlen(opts)-1);
+  Ustrncat(opts, US"\nHELO ", sizeof(opts)-Ustrlen(opts)-1);
   Ustrncat(opts, dcc_helo_option, sizeof(opts)-Ustrlen(opts)-2);
-  Ustrcat(opts, "\n");
+  Ustrcat(opts, US"\n");


   /* initialize the other variables */
   dcchdr = header_list;
@@ -176,8 +176,8 @@ dcc_process(uschar **listptr)
   if (Ustrlen(sender_address) > 0)
     Ustrncpy(from, sender_address, sizeof(from));
   else
-    Ustrncpy(from, "<>", sizeof(from));
-  Ustrncat(from, "\n", sizeof(from)-Ustrlen(from)-1);
+    Ustrncpy(from, US"<>", sizeof(from));
+  Ustrncat(from, US"\n", sizeof(from)-Ustrlen(from)-1);


   /**************************************
    * Now creating the socket connection *
@@ -211,7 +211,7 @@ dcc_process(uschar **listptr)
     /* connecting to the dccifd UNIX socket */
     bzero(&serv_addr, sizeof(serv_addr));
     serv_addr.sun_family = AF_UNIX;
-    Ustrncpy(serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path));
+    Ustrncpy(US serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path));
     if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0){
       DEBUG(D_acl)
         debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
@@ -255,10 +255,10 @@ dcc_process(uschar **listptr)
       bzero(sendbuf, sizeof(sendbuf));
     }
     Ustrncat(sendbuf, recipients_list[i].address, sizeof(sendbuf)-Ustrlen(sendbuf)-1);
-    Ustrncat(sendbuf, "\r\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
+    Ustrncat(sendbuf, US"\r\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
   }
   /* send a blank line between options and message */
-  Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
+  Ustrncat(sendbuf, US"\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
   /* Now we send the input buffer */
   DEBUG(D_acl)
     debug_printf("DCC: %s\nDCC: ****************************\n", sendbuf);
@@ -298,7 +298,7 @@ dcc_process(uschar **listptr)
   }


   /* a blank line separates header from body */
-  Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
+  Ustrncat(sendbuf, US"\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
   flushbuffer(sockfd, sendbuf);
   DEBUG(D_acl)
     debug_printf("\nDCC: ****************************\n%s", sendbuf);
@@ -376,7 +376,7 @@ dcc_process(uschar **listptr)
             if(recvbuf[i] == 'A') {
               DEBUG(D_acl)
                 debug_printf("DCC: Overall result = A\treturning OK\n");
-              Ustrcpy(dcc_return_text, "Mail accepted by DCC");
+              Ustrcpy(dcc_return_text, US"Mail accepted by DCC");
               dcc_result = US"A";
               retval = OK;
             }
@@ -396,7 +396,7 @@ dcc_process(uschar **listptr)
             else if(recvbuf[i] == 'S') {
               DEBUG(D_acl)
                 debug_printf("DCC: Overall result  = S\treturning OK\n");
-              Ustrcpy(dcc_return_text, "Not all recipients accepted by DCC");
+              Ustrcpy(dcc_return_text, US"Not all recipients accepted by DCC");
               /* Since we're in an ACL we want a global result
                * so we accept for all */
               dcc_result = US"A";
@@ -405,7 +405,7 @@ dcc_process(uschar **listptr)
             else if(recvbuf[i] == 'G') {
               DEBUG(D_acl)
                 debug_printf("DCC: Overall result  = G\treturning FAIL\n");
-              Ustrcpy(dcc_return_text, "Greylisted by DCC");
+              Ustrcpy(dcc_return_text, US"Greylisted by DCC");
               dcc_result = US"G";
               retval = FAIL;
             }
@@ -414,7 +414,7 @@ dcc_process(uschar **listptr)
                 debug_printf("DCC: Overall result = T\treturning DEFER\n");
               retval = DEFER;
               log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", recvbuf);
-              Ustrcpy(dcc_return_text, "Temporary error with DCC");
+              Ustrcpy(dcc_return_text, US"Temporary error with DCC");
               dcc_result = US"T";
             }
             else {
@@ -422,7 +422,7 @@ dcc_process(uschar **listptr)
                 debug_printf("DCC: Overall result = something else\treturning DEFER\n");
               retval = DEFER;
               log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", recvbuf);
-              Ustrcpy(dcc_return_text, "Unknown DCC response");
+              Ustrcpy(dcc_return_text, US"Unknown DCC response");
               dcc_result = US"T";
             }
           }
@@ -492,7 +492,7 @@ dcc_process(uschar **listptr)
     if (((xtra_hdrs = expand_string(US"$acl_m_dcc_add_header")) != NULL) && (xtra_hdrs[0] != '\0')) {
       Ustrncpy(dcc_xtra_hdrs, xtra_hdrs, sizeof(dcc_xtra_hdrs) - 2);
       if (dcc_xtra_hdrs[Ustrlen(dcc_xtra_hdrs)-1] != '\n')
-        Ustrcat(dcc_xtra_hdrs, "\n");
+        Ustrcat(dcc_xtra_hdrs, US"\n");
       header_add(' ', "%s", dcc_xtra_hdrs);
       DEBUG(D_acl)
         debug_printf("DCC: adding additional headers in $acl_m_dcc_add_header: %s", dcc_xtra_hdrs);
diff --git a/src/src/debug.c b/src/src/debug.c
index eb62157..de97962 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -230,7 +230,7 @@ if (debug_ptr == debug_buffer)


   if (host_checking && debug_selector == 0)
     {
-    Ustrcpy(debug_ptr, ">>> ");
+    Ustrcpy(debug_ptr, US">>> ");
     debug_ptr += 4;
     }


@@ -242,30 +242,33 @@ if (indent > 0)
   for (int i = indent >> 2; i > 0; i--)
     DEBUG(D_noutf8)
       {
-      Ustrcpy(debug_ptr, "   !");
+      Ustrcpy(debug_ptr, US"   !");
       debug_ptr += 4;    /* 3 spaces + shriek */
       debug_prefix_length += 4;
       }
     else
       {
-      Ustrcpy(debug_ptr, "   " UTF8_VERT_2DASH);
+      Ustrcpy(debug_ptr, US"   " UTF8_VERT_2DASH);
       debug_ptr += 6;    /* 3 spaces + 3 UTF-8 octets */
       debug_prefix_length += 6;
       }


- Ustrncpy(debug_ptr, " ", indent &= 3);
+ Ustrncpy(debug_ptr, US" ", indent &= 3);
debug_ptr += indent;
debug_prefix_length += indent;
}

-/* Use the checked formatting routine to ensure that the buffer
-does not overflow. Ensure there's space for a newline at the end. */
+/* Use the lengthchecked formatting routine to ensure that the buffer
+does not overflow. Ensure there's space for a newline at the end.
+However, use taint-unchecked routines for writing into the buffer
+so that we can write tainted info into the static debug_buffer -
+we trust that we will never expand the results. */

   {
   gstring gs = { .size = (int)sizeof(debug_buffer) - 1,
         .ptr = debug_ptr - debug_buffer,
         .s = debug_buffer };
-  if (!string_vformat(&gs, FALSE, format, ap))
+  if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap))
     {
     uschar * s = US"**** debug string too long - truncated ****\n";
     uschar * p = gs.s + gs.ptr;
diff --git a/src/src/deliver.c b/src/src/deliver.c
index a597c9a..ba9572e 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -144,7 +144,7 @@ Returns:      a pointer to an initialized address_item
 address_item *
 deliver_make_addr(uschar *address, BOOL copy)
 {
-address_item *addr = store_get(sizeof(address_item));
+address_item *addr = store_get(sizeof(address_item), FALSE);
 *addr = address_defaults;
 if (copy) address = string_copy(address);
 addr->address = address;
@@ -1023,7 +1023,8 @@ splitting is done; in those cases use the original field. */


 else
   {
-  uschar * cmp = g->s + g->ptr;
+  uschar * cmp;
+  int off = g->ptr;    /* start of the "full address" */


   if (addr->local_part)
     {
@@ -1045,6 +1046,7 @@ else
   of all, do a caseless comparison; if this succeeds, do a caseful comparison
   on the local parts. */


+  cmp = g->s + off;        /* only now, as rebuffer likely done */
   string_from_gstring(g);    /* ensure nul-terminated */
   if (  strcmpic(cmp, topaddr->address) == 0
      && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
@@ -1137,7 +1139,7 @@ void
 delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
 {
 gstring * g; /* Used for a temporary, expanding buffer, for building log lines  */
-void * reset_point;     /* released afterwards.  */
+rmark reset_point;


/* Log the delivery on the main log. We use an extensible string to build up
the log line, and reset the store afterwards. Remote deliveries should always
@@ -1149,7 +1151,8 @@ pointer to a single host item in their host list, for use by the transport. */
lookup_dnssec_authenticated = NULL;
#endif

-g = reset_point = string_get(256);
+reset_point = store_mark();
+g = string_get_tainted(256, TRUE);    /* addrs will be tainted, so avoid copy */


if (msg)
g = string_append(g, 2, host_and_ident(TRUE), US" ");
@@ -1317,14 +1320,12 @@ static void
deferral_log(address_item * addr, uschar * now,
int logflags, uschar * driver_name, uschar * driver_kind)
{
-gstring * g;
-void * reset_point;
+rmark reset_point = store_mark();
+gstring * g = string_get(256);

/* Build up the line that is used for both the message log and the main
log. */

-g = reset_point = string_get(256);
-
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */

@@ -1396,8 +1397,8 @@ return;
static void
failure_log(address_item * addr, uschar * driver_kind, uschar * now)
{
-void * reset_point;
-gstring * g = reset_point = string_get(256);
+rmark reset_point = store_mark();
+gstring * g = string_get(256);

#ifndef DISABLE_EVENT
/* Message failures for which we will send a DSN get their event raised
@@ -1790,7 +1791,7 @@ if (format)
gstring * g;

   va_start(ap, format);
-  g = string_vformat(NULL, TRUE, CS format, ap);
+  g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS format, ap);
   va_end(ap);
   addr->message = string_from_gstring(g);
   }
@@ -2052,10 +2053,10 @@ Returns:    TRUE if previously delivered by the transport
 static BOOL
 previously_transported(address_item *addr, BOOL testing)
 {
-(void)string_format(big_buffer, big_buffer_size, "%s/%s",
+uschar * s = string_sprintf("%s/%s",
   addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);


-if (tree_search(tree_nonrecipients, big_buffer) != 0)
+if (tree_search(tree_nonrecipients, s) != 0)
   {
   DEBUG(D_deliver|D_route|D_transport)
     debug_printf("%s was previously delivered (%s transport): discarded\n",
@@ -2755,7 +2756,7 @@ while (addr_local)
     f.disable_logging = FALSE;  /* Jic */
     addr->message = addr->router
       ? string_sprintf("No transport set by %s router", addr->router->name)
-      : string_sprintf("No transport set by system filter");
+      : US"No transport set by system filter";
     post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
     continue;
     }
@@ -3066,7 +3067,7 @@ while (addr_local)
     else for (addr2 = addr; addr2; addr2 = addr2->next)
       if (addr2->transport_return == OK)
     {
-    addr3 = store_get(sizeof(address_item));
+    addr3 = store_get(sizeof(address_item), FALSE);
     *addr3 = *addr2;
     addr3->next = NULL;
     addr3->shadow_message = US &addr2->shadow_message;
@@ -3464,7 +3465,7 @@ while (!done)


       if (!r || !(*ptr & rf_delete))
     {
-    r = store_get(sizeof(retry_item));
+    r = store_get(sizeof(retry_item), FALSE);
     r->next = addr->retries;
     addr->retries = r;
     r->flags = *ptr++;
@@ -3647,7 +3648,7 @@ while (!done)


       if (*ptr)
         {
-        h = store_get(sizeof(host_item));
+        h = store_get(sizeof(host_item), FALSE);
         h->name = string_copy(ptr);
         while (*ptr++);
         h->address = string_copy(ptr);
@@ -4231,7 +4232,7 @@ set up, do so. */


 if (!parlist)
   {
-  parlist = store_get(remote_max_parallel * sizeof(pardata));
+  parlist = store_get(remote_max_parallel * sizeof(pardata), FALSE);
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
     parlist[poffset].pid = 0;
   }
@@ -5117,7 +5118,7 @@ where they are locally interpreted. [The new draft "821" is more explicit on
 this, Jan 1999.] We know the syntax is valid, so this can be done by simply
 removing quoting backslashes and any unquoted doublequotes. */


-t = addr->cc_local_part = store_get(len+1);
+t = addr->cc_local_part = store_get(len+1, is_tainted(address));
while(len-- > 0)
{
int c = *address++;
@@ -5160,7 +5161,7 @@ if (percent_hack_domains)

   if (new_address)
     {
-    address_item *new_parent = store_get(sizeof(address_item));
+    address_item *new_parent = store_get(sizeof(address_item), FALSE);
     *new_parent = *addr;
     addr->parent = new_parent;
     new_parent->child_count = 1;
@@ -6028,8 +6029,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)


   if (addr_new)
     {
-    int uid = (system_filter_uid_set)? system_filter_uid : geteuid();
-    int gid = (system_filter_gid_set)? system_filter_gid : getegid();
+    int uid = system_filter_uid_set ? system_filter_uid : geteuid();
+    int gid = system_filter_gid_set ? system_filter_gid : getegid();


     /* The text "system-filter" is tested in transport_set_up_command() and in
     set_up_shell_command() in the pipe transport, to enable them to permit
@@ -6103,6 +6104,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
           if (!tmp)
             p->message = string_sprintf("failed to expand \"%s\" as a "
               "system filter transport name", tpname);
+      if (is_tainted(tmp))
+            p->message = string_sprintf("attempt to used tainted value '%s' for"
+          "transport '%s' as a system filter", tmp, tpname);
           tpname = tmp;
           }
         else
@@ -6411,10 +6415,8 @@ while (addr_new)           /* Loop until all addresses dealt with */
       keep piling '>' characters on the front. */


       if (addr->address[0] == '>')
-        {
         while (tree_search(tree_duplicates, addr->unique))
           addr->unique = string_sprintf(">%s", addr->unique);
-        }


       else if ((tnode = tree_search(tree_duplicates, addr->unique)))
         {
@@ -6822,8 +6824,8 @@ while (addr_new)           /* Loop until all addresses dealt with */
          &addr_succeed, v_none)) == DEFER)
       retry_add_item(addr,
         addr->router->retry_use_local_part
-        ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
-    : string_sprintf("R:%s", addr->domain),
+      ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
+      : string_sprintf("R:%s", addr->domain),
     0);


     /* Otherwise, if there is an existing retry record in the database, add
@@ -7318,7 +7320,7 @@ for (address_item * a = addr_succeed; a; a = a->next)
     {
     /* copy and relink address_item and send report with all of them at once later */
     address_item * addr_next = addr_senddsn;
-    addr_senddsn = store_get(sizeof(address_item));
+    addr_senddsn = store_get(sizeof(address_item), FALSE);
     *addr_senddsn = *a;
     addr_senddsn->next = addr_next;
     }
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 715774c..a410ed5 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -43,8 +43,12 @@ static const uschar * dkim_collect_error = NULL;
 uschar *
 dkim_exim_query_dns_txt(uschar * name)
 {
+/*XXX need to always alloc the dnsa, from tainted mem.
+Then, we hope, the answers will be tainted */
+
 dns_answer dnsa;
 dns_scan dnss;
+rmark reset_point = store_mark();
 gstring * g = NULL;


 lookup_dnssec_authenticated = NULL;
@@ -84,7 +88,7 @@ for (dns_record * rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     }


 bad:
-if (g) store_reset(g);
+store_reset(reset_point);
 return NULL;    /*XXX better error detail?  logging? */
 }


diff --git a/src/src/dmarc.c b/src/src/dmarc.c
index 5bf3303..be9c4b3 100644
--- a/src/src/dmarc.c
+++ b/src/src/dmarc.c
@@ -398,11 +398,11 @@ if (!dmarc_abort && !sender_host_authenticated)
/* Can't use exim's string manipulation functions so allocate memory
for libopendmarc using its max hostname length definition. */

-  dmarc_domain = US calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
+  dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, TRUE);
   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
     dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
-  dmarc_used_domain = string_copy(dmarc_domain);
-  free(dmarc_domain);
+  store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
+  dmarc_used_domain = dmarc_domain;


   if (libdm_status != DMARC_PARSE_OKAY)
     log_write(0, LOG_MAIN|LOG_PANIC,
diff --git a/src/src/dns.c b/src/src/dns.c
index 6ef6b77..e384597 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -40,7 +40,7 @@ fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
 {
 int len = Ustrlen(domain);
 int asize = size;                  /* Locally modified */
-uschar name[256];
+uschar * name;
 uschar utilname[256];
 uschar *aptr = answerptr;          /* Locally modified */
 struct stat statbuf;
@@ -48,8 +48,7 @@ struct stat statbuf;
 /* Remove terminating dot. */


if (domain[len - 1] == '.') len--;
-Ustrncpy(name, domain, len);
-name[len] = 0;
+name = string_copyn(domain, len);

/* Look for the fakens utility, and if it exists, call it. */

@@ -249,7 +248,7 @@ if (Ustrchr(string, ':') == NULL)
     *pp++ = '.';
     p = ppp - 1;
     }
-  Ustrcpy(pp, "in-addr.arpa");
+  Ustrcpy(pp, US"in-addr.arpa");
   }


 /* Handle IPv6 address; convert to binary so as to fill out any
@@ -268,7 +267,7 @@ else
   for (int i = 3; i >= 0; i--)
     for (int j = 0; j < 32; j += 4)
       pp += sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
-  Ustrcpy(pp, "ip6.arpa.");
+  Ustrcpy(pp, US"ip6.arpa.");


   /* Another way of doing IPv6 reverse lookups was proposed in conjunction
   with A6 records. However, it fell out of favour when they did. The
@@ -287,7 +286,7 @@ else
     sprintf(pp, "%08X", v6[i]);
     pp += 8;
     }
-  Ustrcpy(pp, "].ip6.arpa.");
+  Ustrcpy(pp, US"].ip6.arpa.");
   **************************************************/


   }
@@ -615,7 +614,7 @@ Returns:     the return code
 static int
 dns_return(const uschar * name, int type, int rc)
 {
-tree_node *node = store_get_perm(sizeof(tree_node) + 290);
+tree_node *node = store_get_perm(sizeof(tree_node) + 290, TRUE);
 dns_fail_tag(node->name, name, type);
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
@@ -947,7 +946,8 @@ for (int i = 0; i <= dns_cname_loops; i++)
   if (!cname_rr.data)
     return DNS_FAIL;


-  data = store_get(256);
+  /* DNS data comes from the outside, hence tainted */
+  data = store_get(256, TRUE);
   if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
       cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0)
     return DNS_FAIL;
@@ -1201,7 +1201,8 @@ if (rr->type == T_A)
   uschar *p = US rr->data;
   if (p + 4 <= dnsa_lim)
     {
-    yield = store_get(sizeof(dns_address) + 20);
+    /* the IP is not regarded as tainted */
+    yield = store_get(sizeof(dns_address) + 20, FALSE);
     (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
     yield->next = NULL;
     }
@@ -1215,7 +1216,7 @@ else
     {
     struct in6_addr in6;
     for (int i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i];
-    yield = store_get(sizeof(dns_address) + 50);
+    yield = store_get(sizeof(dns_address) + 50, FALSE);
     inet_ntop(AF_INET6, &in6, CS yield->address, 50);
     yield->next = NULL;
     }
diff --git a/src/src/drtables.c b/src/src/drtables.c
index b702429..0597562 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -509,7 +509,7 @@ static struct lookupmodulestr *lookupmodules = NULL;
 static void
 addlookupmodule(void *dl, struct lookup_module_info *info)
 {
-struct lookupmodulestr *p = store_malloc(sizeof(struct lookupmodulestr));
+struct lookupmodulestr *p = store_get(sizeof(struct lookupmodulestr), FALSE);


p->dl = dl;
p->info = info;
@@ -620,12 +620,12 @@ init_lookup_list(void)
int countmodules = 0;
int moduleerrors = 0;
#endif
- struct lookupmodulestr *p;
static BOOL lookup_list_init_done = FALSE;
-
+ rmark reset_point;

   if (lookup_list_init_done)
     return;
+  reset_point = store_mark();
   lookup_list_init_done = TRUE;


#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
@@ -787,17 +787,10 @@ init_lookup_list(void)
memset(lookup_list, 0, sizeof(lookup_info *) * lookup_list_count);

   /* now add all lookups to the real list */
-  p = lookupmodules;
-  while (p) {
-    struct lookupmodulestr *pnext;
-
+  for (struct lookupmodulestr * p = lookupmodules; p; p = p->next)
     for (int j = 0; j < p->info->lookupcount; j++)
       add_lookup_to_list(p->info->lookups[j]);
-
-    pnext = p->next;
-    store_free(p);
-    p = pnext;
-  }
+  store_reset(reset_point);
   /* just to be sure */
   lookupmodules = NULL;
 }
diff --git a/src/src/dummies.c b/src/src/dummies.c
index 2e1ad11..197415f 100644
--- a/src/src/dummies.c
+++ b/src/src/dummies.c
@@ -20,7 +20,7 @@ alternates. */
 /* We don't have the full Exim headers dragged in, but this function
 is used for debugging output. */


-extern gstring * string_vformat(gstring *, BOOL, const char *, va_list);
+extern gstring * string_vformat(gstring *, unsigned, const char *, va_list);


 /*************************************************
@@ -56,7 +56,9 @@ flags = flags;
 *      Handle calls to print debug output        *
 *************************************************/


-/* The message just gets written to stderr
+/* The message just gets written to stderr.
+We use tainted memory to format into just so that we can handle
+tainted arguments.

 Arguments:
   format    a printf() format
@@ -69,12 +71,12 @@ void
 debug_printf(char *format, ...)
 {
 va_list ap;
-gstring * g = string_get(1024);
-void * reset_point = g;
+rmark reset_point = store_mark();
+gstring * g = string_get_tainted(1024, TRUE);


va_start(ap, format);

-if (!string_vformat(g, FALSE, format, ap))
+if (!string_vformat(g, 0, format, ap))
{
char * s = "**** debug string overflowed buffer ****\n";
char * p = CS g->s + g->ptr;
diff --git a/src/src/environment.c b/src/src/environment.c
index 9d3d126..c29cc6c 100644
--- a/src/src/environment.c
+++ b/src/src/environment.c
@@ -37,6 +37,8 @@ if (!keep_environment || *keep_environment == '\0')

   }
 else if (Ustrcmp(keep_environment, "*") != 0)
+  {
+  rmark reset_point = store_mark();
   if (environ) for (uschar ** p = USS environ; *p; /* see below */)
     {
     /* It's considered broken if we do not find the '=', according to
@@ -53,17 +55,18 @@ else if (Ustrcmp(keep_environment, "*") != 0)
         if (os_unsetenv(name) < 0) return FALSE;
         else p = USS environ; /* RESTART from the beginning */
       else p++;
-      store_reset(name);
       }
     }
+  store_reset(reset_point);
+  }
 if (add_environment)
   {
-    uschar * p;
-    int sep = 0;
-    const uschar * envlist = add_environment;
+  uschar * p;
+  int sep = 0;
+  const uschar * envlist = add_environment;


-    while ((p = string_nextinlist(&envlist, &sep, NULL, 0))) putenv(CS p);
+  while ((p = string_nextinlist(&envlist, &sep, NULL, 0))) putenv(CS p);
   }


- return TRUE;
+return TRUE;
}
diff --git a/src/src/exim.c b/src/src/exim.c
index 7571705..f163b12 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -42,7 +42,9 @@ regular expression for a long time; the other for short-term use. */
static void *
function_store_get(size_t size)
{
-return store_get((int)size);
+/* For now, regard all RE results as potentially tainted. We might need
+more intelligence on this point. */
+return store_get((int)size, TRUE);
}

 static void
@@ -181,7 +183,7 @@ va_list ap;
 g = string_fmt_append(&gs, "%5d ", (int)getpid());
 len = g->ptr;
 va_start(ap, format);
-if (!string_vformat(g, FALSE, format, ap))
+if (!string_vformat(g, 0, format, ap))
   {
   gs.ptr = len;
   g = string_cat(&gs, US"**** string overflowed buffer ****");
@@ -502,7 +504,7 @@ for (int i = 0; i <= 2; i++)
     {
     if (devnull < 0) devnull = open("/dev/null", O_RDWR);
     if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
-      string_open_failed(errno, "/dev/null"));
+      string_open_failed(errno, "/dev/null", NULL));
     if (devnull != i) (void)dup2(devnull, i);
     }
   }
@@ -666,6 +668,7 @@ void
 exim_exit(int rc, const uschar * process)
 {
 search_tidyup();
+store_exit();
 DEBUG(D_any)
   debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
     ">>>>>>>>>>>>>>>>\n", (int)getpid(),
@@ -674,6 +677,14 @@ exit(rc);
 }



+void
+exim_underbar_exit(int rc)
+{
+store_exit();
+_exit(rc);
+}
+
+

/* Print error string, then die */
static void
@@ -1254,10 +1265,10 @@ for (int i = 0;; i++)

   #ifdef USE_READLINE
   char *readline_line = NULL;
-  if (fn_readline != NULL)
+  if (fn_readline)
     {
-    if ((readline_line = fn_readline((i > 0)? "":"> ")) == NULL) break;
-    if (*readline_line != 0 && fn_addhist != NULL) fn_addhist(readline_line);
+    if (!(readline_line = fn_readline((i > 0)? "":"> "))) break;
+    if (*readline_line != 0 && fn_addhist) fn_addhist(readline_line);
     p = US readline_line;
     }
   else
@@ -1276,9 +1287,7 @@ for (int i = 0;; i++)
   while (ss > p && isspace(ss[-1])) ss--;


   if (i > 0)
-    {
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
-    }


g = string_catn(g, p, ss - p);

@@ -1375,7 +1384,7 @@ if ( ! ((real_uid == root_uid)
}

/* Get a list of macros which are whitelisted */
-whitelisted = string_copy_malloc(US WHITELIST_D_MACROS);
+whitelisted = string_copy_perm(US WHITELIST_D_MACROS, FALSE);
prev_char_item = FALSE;
white_count = 0;
for (p = whitelisted; *p != '\0'; ++p)
@@ -1562,7 +1571,7 @@ uschar *malware_test_file = NULL;
uschar *real_sender_address;
uschar *originator_home = US"/";
size_t sz;
-void *reset_point;
+rmark reset_point;

struct passwd *pw;
struct stat statbuf;
@@ -1691,6 +1700,7 @@ big_buffer = store_malloc(big_buffer_size);
/* Set up the handler for the data request signal, and set the initial
descriptive text. */

+process_info = store_get(PROCESS_INFO_SIZE, TRUE);    /* tainted */
 set_process_info("initializing");
 os_restarting_signal(SIGUSR1, usr1_handler);


@@ -2318,7 +2328,7 @@ for (i = 1; i < argc; i++)
         else
               {
               /* Well, the trust list at least is up to scratch... */
-              void *reset_point = store_get(0);
+              rmark reset_point = store_mark();
               uschar *trusted_configs[32];
               int nr_configs = 0;
               int i = 0;
@@ -2348,30 +2358,22 @@ for (i = 1; i < argc; i++)
                         &sep, big_buffer, big_buffer_size)) != NULL)
                   {
                   for (i=0; i < nr_configs; i++)
-                    {
                     if (Ustrcmp(filename, trusted_configs[i]) == 0)
                       break;
-                    }
                   if (i == nr_configs)
                     {
                     f.trusted_config = FALSE;
                     break;
                     }
                   }
-                store_reset(reset_point);
                 }
-              else
-                {
-                /* No valid prefixes found in trust_list file. */
+              else    /* No valid prefixes found in trust_list file. */
                 f.trusted_config = FALSE;
-                }
+              store_reset(reset_point);
               }
         }
-          else
-            {
-            /* Could not open trust_list file. */
+          else        /* Could not open trust_list file. */
             f.trusted_config = FALSE;
-            }
           }
       #else
         /* Not root; don't trust config */
@@ -2426,8 +2428,8 @@ for (i = 1; i < argc; i++)


       if (clmacro_count >= MAX_CLMACROS)
         exim_fail("exim: too many -D options on command line\n");
-      clmacros[clmacro_count++] = string_sprintf("-D%s=%s", m->name,
-        m->replacement);
+      clmacros[clmacro_count++] =
+    string_sprintf("-D%s=%s", m->name, m->replacement);
       }
     #endif
     break;
@@ -2537,7 +2539,7 @@ for (i = 1; i < argc; i++)
           { badarg = TRUE; break; }
         }
       if (*argrest == 0)
-        sender_address = string_sprintf("");  /* Ensure writeable memory */
+        *(sender_address = store_get(1, FALSE)) = '\0';  /* Ensure writeable memory */
       else
         {
         uschar *temp = argrest + Ustrlen(argrest) - 1;
@@ -2550,6 +2552,7 @@ for (i = 1; i < argc; i++)
 #endif
         sender_address = parse_extract_address(argrest, &errmess,
           &dummy_start, &dummy_end, &sender_address_domain, TRUE);
+    sender_address = string_copy_taint(sender_address, TRUE);
 #ifdef SUPPORT_I18N
     message_smtputf8 =  string_is_utf8(sender_address);
     allow_utf8_domains = FALSE;
@@ -2995,11 +2998,13 @@ for (i = 1; i < argc; i++)


       /* -oMas: setting authenticated sender */


-      else if (Ustrcmp(argrest, "Mas") == 0) authenticated_sender = argv[++i];
+      else if (Ustrcmp(argrest, "Mas") == 0)
+    authenticated_sender = string_copy_taint(argv[++i], TRUE);


       /* -oMai: setting authenticated id */


-      else if (Ustrcmp(argrest, "Mai") == 0) authenticated_id = argv[++i];
+      else if (Ustrcmp(argrest, "Mai") == 0)
+    authenticated_id = string_copy_taint(argv[++i], TRUE);


       /* -oMi: Set incoming interface address */


@@ -3027,7 +3032,8 @@ for (i = 1; i < argc; i++)

       /* -oMs: Set sender host name */


-      else if (Ustrcmp(argrest, "Ms") == 0) sender_host_name = argv[++i];
+      else if (Ustrcmp(argrest, "Ms") == 0)
+    sender_host_name = string_copy_taint(argv[++i], TRUE);


       /* -oMt: Set sender ident */


@@ -3933,7 +3939,7 @@ if (  (debug_selector & D_any  ||  LOGGING(arguments))
    && f.really_exim && !list_options && !checking)
   {
   uschar *p = big_buffer;
-  Ustrcpy(p, "cwd= (failed)");
+  Ustrcpy(p, US"cwd= (failed)");


   if (!initial_cwd)
     p += 13;
@@ -3955,9 +3961,9 @@ if (  (debug_selector & D_any  ||  LOGGING(arguments))
     uschar *quote;
     if (p + len + 8 >= big_buffer + big_buffer_size)
       {
-      Ustrcpy(p, " ...");
+      Ustrcpy(p, US" ...");
       log_write(0, LOG_MAIN, "%s", big_buffer);
-      Ustrcpy(big_buffer, "...");
+      Ustrcpy(big_buffer, US"...");
       p = big_buffer + 3;
       }
     printing = string_printing(argv[i]);
@@ -4290,15 +4296,6 @@ needed in transports so we lost the optimisation. */


readconf_rest();

-/* The configuration data will have been read into POOL_PERM because we won't
-ever want to reset back past it. Change the current pool to POOL_MAIN. In fact,
-this is just a bit of pedantic tidiness. It wouldn't really matter if the
-configuration were read into POOL_MAIN, because we don't do any resets till
-later on. However, it seems right, and it does ensure that both pools get used.
-*/
-
-store_pool = POOL_MAIN;
-
 /* Handle the -brt option. This is for checking out retry configurations.
 The next three arguments are a domain name or a complete address, and
 optionally two error numbers. All it does is to call the function that
@@ -4495,7 +4492,7 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD)
     else if ((pid = fork()) == 0)
       {
       (void)deliver_message(argv[i], forced_delivery, deliver_give_up);
-      _exit(EXIT_SUCCESS);
+      exim_underbar_exit(EXIT_SUCCESS);
       }
     else if (pid < 0)
       {
@@ -4664,8 +4661,8 @@ if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
 the caller. This will get overwritten below for an inetd call. If a trusted
 caller has set it empty, unset it. */


-if (sender_ident == NULL) sender_ident = originator_login;
- else if (sender_ident[0] == 0) sender_ident = NULL;
+if (!sender_ident) sender_ident = originator_login;
+else if (!*sender_ident) sender_ident = NULL;

/* Handle the -brw option, which is for checking out rewriting rules. Cause log
writes (on errors) to go to stderr instead. Can't do this earlier, as want the
@@ -4687,8 +4684,8 @@ if (test_rewrite_arg >= 0)
unless a trusted caller supplies a sender address with -f, or is passing in the
message via SMTP (inetd invocation or otherwise). */

-if ((sender_address == NULL && !smtp_input) ||
-    (!f.trusted_caller && filter_test == FTEST_NONE))
+if (  !sender_address && !smtp_input
+   || !f.trusted_caller && filter_test == FTEST_NONE)
   {
   f.sender_local = TRUE;


@@ -4696,10 +4693,10 @@ if ((sender_address == NULL && !smtp_input) ||
via -oMas and -oMai and if so, they will already be set. Otherwise, force
defaults except when host checking. */

-  if (authenticated_sender == NULL && !host_checking)
+  if (!authenticated_sender && !host_checking)
     authenticated_sender = string_sprintf("%s@%s", originator_login,
       qualify_domain_sender);
-  if (authenticated_id == NULL && !host_checking)
+  if (!authenticated_id && !host_checking)
     authenticated_id = originator_login;
   }


@@ -4709,8 +4706,8 @@ is specified is the empty address. However, if a trusted caller does not
specify a sender address for SMTP input, we leave sender_address unset. This
causes the MAIL commands to be honoured. */

-if ((!smtp_input && sender_address == NULL) ||
-    !receive_check_set_sender(sender_address))
+if (  !smtp_input && !sender_address
+   || !receive_check_set_sender(sender_address))
   {
   /* Either the caller is not permitted to set a general sender, or this is
   non-SMTP input and the trusted caller has not set a sender. If there is no
@@ -4736,8 +4733,7 @@ f.sender_set_untrusted = sender_address != originator_login && !f.trusted_caller
 address, which indicates an error message, or doesn't exist (root caller, smtp
 interface, no -f argument). */


-if (sender_address != NULL && sender_address[0] != 0 &&
-    sender_address_domain == 0)
+if (sender_address && *sender_address && sender_address_domain == 0)
   sender_address = string_sprintf("%s@%s", local_part_quote(sender_address),
     qualify_domain_sender);


@@ -4927,7 +4923,7 @@ if (host_checking)
it. The code works for both IPv4 and IPv6, as it happens. */

size = host_aton(sender_host_address, x);
- sender_host_address = store_get(48); /* large enough for full IPv6 */
+ sender_host_address = store_get(48, FALSE); /* large enough for full IPv6 */
(void)host_nmtoa(size, x, -1, sender_host_address, ':');

/* Now set up for testing */
@@ -4957,7 +4953,7 @@ if (host_checking)

   if (smtp_start_session())
     {
-    for (reset_point = store_get(0); ; store_reset(reset_point))
+    for (; (reset_point = store_mark()); store_reset(reset_point))
       {
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
@@ -5200,7 +5196,6 @@ if (!f.synchronous_delivery)
 /* Save the current store pool point, for resetting at the start of
 each message, and save the real sender address, if any. */


-reset_point = store_get(0);
real_sender_address = sender_address;

/* Loop to receive messages; receive_msg() returns TRUE if there are more
@@ -5209,6 +5204,7 @@ collapsed). */

while (more)
{
+ reset_point = store_mark();
message_id[0] = 0;

   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
@@ -5358,7 +5354,7 @@ while (more)
             }
           }


-        receive_add_recipient(recipient, -1);
+        receive_add_recipient(string_copy_taint(recipient, TRUE), -1);
         s = ss;
         if (!finished)
           while (*(++s) != 0 && (*s == ',' || isspace(*s)));
@@ -5580,8 +5576,8 @@ while (more)


       rc = deliver_message(message_id, FALSE, FALSE);
       search_tidyup();
-      _exit((!mua_wrapper || rc == DELIVER_MUA_SUCCEEDED)?
-        EXIT_SUCCESS : EXIT_FAILURE);
+      exim_underbar_exit(!mua_wrapper || rc == DELIVER_MUA_SUCCEEDED
+        ? EXIT_SUCCESS : EXIT_FAILURE);
       }


     if (pid < 0)
diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c
index 39207be..311d961 100644
--- a/src/src/exim_dbmbuild.c
+++ b/src/src/exim_dbmbuild.c
@@ -34,10 +34,10 @@ uschar * spool_directory = NULL;    /* dummy for dbstuff.h */


                     /* dummies needed by Solaris build */
 void *
-store_get_3(int size, const char *filename, int linenumber)
+store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
 { return NULL; }
-void
-store_reset_3(void *ptr, const char *filename, int linenumber)
+void **
+store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
 { }



@@ -213,14 +213,14 @@ if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
exit(1);
}

-Ustrcpy(temp_dbmname, argv[arg+1]);
-Ustrcat(temp_dbmname, ".dbmbuild_temp");
+Ustrcpy(temp_dbmname, US argv[arg+1]);
+Ustrcat(temp_dbmname, US".dbmbuild_temp");

Ustrcpy(dirname, temp_dbmname);
if ((bptr = Ustrrchr(dirname, '/')))
*bptr = '\0';
else
- Ustrcpy(dirname, ".");
+ Ustrcpy(dirname, US".");

/* It is apparently necessary to open with O_RDWR for this to work
with gdbm-1.7.3, though no reading is actually going to be done. */
@@ -441,7 +441,7 @@ if (yield == 0 || yield == 1)

   #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
   Ustrcpy(real_dbmname, temp_dbmname);
-  Ustrcpy(buffer, argv[arg+1]);
+  Ustrcpy(buffer, US argv[arg+1]);
   if (Urename(real_dbmname, buffer) != 0)
     {
     printf("Unable to rename %s as %s\n", real_dbmname, buffer);
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 2c7aad6..8b71a41 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -376,7 +376,7 @@ dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
 void *yield;
 EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));


memcpy(key_copy, key, klen);

@@ -387,7 +387,10 @@ EXIM_DATUM_SIZE(key_datum) = klen;

if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;

-yield = store_get(EXIM_DATUM_SIZE(result_datum));
+/* Assume for now that anything stored could have been tainted. Properly
+we should store the taint status along with the data. */
+
+yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);

@@ -420,7 +423,7 @@ dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
EXIM_DATUM key_datum, value_datum;
dbdata_generic *gptr = (dbdata_generic *)ptr;
int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));

memcpy(key_copy, key, klen);
gptr->time_stamp = time(NULL);
@@ -452,7 +455,7 @@ int
dbfn_delete(open_db *dbblock, const uschar *key)
{
int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));

memcpy(key_copy, key, klen);
EXIM_DATUM key_datum;
@@ -551,6 +554,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
uschar *t;
uschar name[MESSAGE_ID_LENGTH + 1];
void *value;
+ rmark reset_point = store_mark();

   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
   which might change. */
@@ -684,8 +688,8 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
     printf("  %s %.*s\n", keybuffer, length, session->session);
     break;
       }
-    store_reset(value);
     }
+  store_reset(reset_point);
   }


dbfn_close(dbm);
@@ -735,7 +739,7 @@ int dbdata_type;
uschar **argv = USS cargv;
uschar buffer[256];
uschar name[256];
-void *reset_point = store_get(0);
+rmark reset_point;

name[0] = 0; /* No name set */

@@ -745,7 +749,7 @@ user requests */
dbdata_type = check_args(argc, argv, US"fixdb", US"");
printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);

-for(;;)
+for(; (reset_point = store_mark()); store_reset(reset_point))
{
open_db dbblock;
open_db *dbm;
@@ -760,8 +764,6 @@ for(;;)
uschar *t;
uschar field[256], value[256];

- store_reset(reset_point);
-
printf("> ");
if (Ufgets(buffer, 256, stdin) == NULL) break;

@@ -1100,7 +1102,7 @@ struct stat statbuf;
 int maxkeep = 30 * 24 * 60 * 60;
 int dbdata_type, i, oldest, path_len;
 key_item *keychain = NULL;
-void *reset_point;
+rmark reset_point;
 open_db dbblock;
 open_db *dbm;
 EXIM_CURSOR *cursor;
@@ -1173,7 +1175,7 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
      key;
      key = dbfn_scan(dbm, FALSE, &cursor))
   {
-  key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
+  key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
   k->next = keychain;
   keychain = k;
   Ustrcpy(k->key, key);
@@ -1182,13 +1184,10 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
 /* Now scan the collected keys and operate on the records, resetting
 the store each time round. */


-reset_point = store_get(0);
-
-while (keychain)
+for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
{
dbdata_generic *value;

-  store_reset(reset_point);
   key = keychain->key;
   keychain = keychain->next;
   value = dbfn_read_with_length(dbm, key, NULL);
diff --git a/src/src/expand.c b/src/src/expand.c
index 65c585d..1bfc75d 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -1856,22 +1856,17 @@ switch (vp->type)
     return sender_host_name ? sender_host_name : US"";


   case vtype_localpart:                      /* Get local part from address */
-    s = *((uschar **)(val));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    if (domain == NULL) return s;
+    if (!(s = *((uschar **)(val)))) return US"";
+    if (!(domain = Ustrrchr(s, '@'))) return s;
     if (domain - s > sizeof(var_buffer) - 1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
       " in string expansion", sizeof(var_buffer));
-    Ustrncpy(var_buffer, s, domain - s);
-    var_buffer[domain - s] = 0;
-    return var_buffer;
+    return string_copyn(s, domain - s);


   case vtype_domain:                         /* Get domain from address */
-    s = *((uschar **)(val));
-    if (s == NULL) return US"";
+    if (!(s = *((uschar **)(val)))) return US"";
     domain = Ustrrchr(s, '@');
-    return (domain == NULL)? US"" : domain + 1;
+    return domain ? domain + 1 : US"";


   case vtype_msgheaders:
     return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);
@@ -2354,7 +2349,7 @@ switch(cond_type = identify_operator(&s, &opname))
       return NULL;
       }


-    s = read_name(name, 256, s+1, US"_");
+    s = read_name(name, sizeof(name), s+1, US"_");


     /* Test for a header's existence. If the name contains a closing brace
     character, this may be a user error where the terminating colon has been
@@ -2366,7 +2361,7 @@ switch(cond_type = identify_operator(&s, &opname))
        && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
        )
       {
-      s = read_header_name(name, 256, s);
+      s = read_header_name(name, sizeof(name), s);
       /* {-for-text-editors */
       if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
       if (yield) *yield =
@@ -2380,9 +2375,9 @@ switch(cond_type = identify_operator(&s, &opname))
       {
       if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
     {
-    expand_string_message = (name[0] == 0)?
-      string_sprintf("variable name omitted after \"def:\"") :
-      string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+    expand_string_message = name[0]
+      ? string_sprintf("unknown variable \"%s\" after \"def:\"", name)
+      : US"variable name omitted after \"def:\"";
     check_variable_error_message(name);
     return NULL;
     }
@@ -3135,7 +3130,7 @@ switch(cond_type = identify_operator(&s, &opname))
       return NULL;
       }


-      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item);
+      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", opname, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
@@ -3570,7 +3565,7 @@ Returns:  pointer to string containing the last three
 static uschar *
 prvs_daystamp(int day_offset)
 {
-uschar *days = store_get(32);                /* Need at least 24 for cases */
+uschar *days = store_get(32, FALSE);         /* Need at least 24 for cases */
 (void)string_format(days, 32, TIME_T_FMT,    /* where TIME_T_FMT is %lld */
   (time(NULL) + day_offset*86400)/86400);
 return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100";
@@ -3607,7 +3602,7 @@ uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
 uschar outerkey[64];
-uschar *finalhash_hex = store_get(40);
+uschar *finalhash_hex;


if (key_num == NULL)
key_num = US"0";
@@ -3640,7 +3635,9 @@ chash_start(HMAC_SHA1, &h);
chash_mid(HMAC_SHA1, &h, outerkey);
chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash);

-p = finalhash_hex;
+/* Hashing is deemed sufficient to de-taint any input data */
+
+p = finalhash_hex = store_get(40, FALSE);
for (int i = 0; i < 3; i++)
{
*p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
@@ -4100,6 +4097,7 @@ static uschar *
expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
{
+rmark reset_point = store_mark();
gstring * yield = string_get(Ustrlen(string) + 64);
int item_type;
const uschar *s = string;
@@ -4122,6 +4120,14 @@ DEBUG(D_expand)
f.expand_string_forcedfail = FALSE;
expand_string_message = US"";

+if (is_tainted(string))
+  {
+  expand_string_message =
+    string_sprintf("attempt to expand tainted string '%s'", s);
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+  goto EXPAND_FAILED;
+  }
+
 while (*s != 0)
   {
   uschar *value;
@@ -4193,12 +4199,13 @@ while (*s != 0)
     buffer. */


     if (!yield)
-      g = store_get(sizeof(gstring));
+      g = store_get(sizeof(gstring), FALSE);
     else if (yield->ptr == 0)
       {
-      if (resetok) store_reset(yield);
+      if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
-      g = store_get(sizeof(gstring));    /* alloc _before_ calling find_variable() */
+      reset_point = store_mark();
+      g = store_get(sizeof(gstring), FALSE);    /* alloc _before_ calling find_variable() */
       }


     /* Header */
@@ -5380,7 +5387,7 @@ while (*s != 0)
           {
           if (sigalrm_seen || runrc == -256)
             {
-            expand_string_message = string_sprintf("command timed out");
+            expand_string_message = US"command timed out";
             killpg(pid, SIGKILL);       /* Kill the whole process group */
             }


@@ -5922,8 +5929,8 @@ while (*s != 0)
         while (isspace(*s)) s++;
         if (*s != ':')
           {
-          expand_string_message = string_sprintf(
-            "missing object value-separator for extract json");
+          expand_string_message =
+            US"missing object value-separator for extract json";
           goto EXPAND_FAILED_CURLY;
           }
         s++;
@@ -6421,8 +6428,8 @@ while (*s != 0)
     }


       xtract = s;
-      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
-      if (!tmp) goto EXPAND_FAILED;
+      if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok)))
+    goto EXPAND_FAILED;
       xtract = string_copyn(xtract, s - xtract);


       if (*s++ != '}')
@@ -6484,6 +6491,7 @@ while (*s != 0)
         newlist = string_append_listele(newlist, sep, dstitem);
         newkeylist = string_append_listele(newkeylist, sep, dstfield);


+/*XXX why field-at-a-time copy?  Why not just dup the rest of the list? */
         while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
           {
           if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
@@ -6579,7 +6587,7 @@ while (*s != 0)
           log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
           goto EXPAND_FAILED;
           }
-        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]));
+        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0]));
         Ustrcpy(t->name, argv[0]);
         t->data.ptr = handle;
         (void)tree_insertnode(&dlobj_anchor, t);
@@ -7051,7 +7059,7 @@ while (*s != 0)
       case 'h': t = tree_search(hostlist_anchor,      sub); suffix = US"_h"; break;
       case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
       default:
-            expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+            expand_string_message = US"bad suffix on \"list\" operator";
         goto EXPAND_FAILED;
       }


@@ -7875,12 +7883,13 @@ while (*s != 0)
     gstring * g = NULL;


     if (!yield)
-      g = store_get(sizeof(gstring));
+      g = store_get(sizeof(gstring), FALSE);
     else if (yield->ptr == 0)
       {
-      if (resetok) store_reset(yield);
+      if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
-      g = store_get(sizeof(gstring));    /* alloc _before_ calling find_variable() */
+      reset_point = store_mark();
+      g = store_get(sizeof(gstring), FALSE);    /* alloc _before_ calling find_variable() */
       }
     if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
@@ -7934,15 +7943,20 @@ if (left) *left = s;
 In many cases the final string will be the first one that was got and so there
 will be optimal store usage. */


-if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1));
+if (resetok) gstring_release_unused(yield);
else if (resetok_p) *resetok_p = FALSE;

 DEBUG(D_expand)
+  {
+  BOOL tainted = is_tainted(yield->s);
   DEBUG(D_noutf8)
     {
     debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
     debug_printf_indent("%sresult: %s\n",
       skipping ? "|-----" : "\\_____", yield->s);
+    if (tainted)
+      debug_printf_indent("%s     \\__(tainted)\n",
+    skipping ? "|     " : "      ");
     if (skipping)
       debug_printf_indent("\\___skipping: result is not used\n");
     }
@@ -7951,15 +7965,19 @@ DEBUG(D_expand)
     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
+    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->s);
+    if (tainted)
+      debug_printf_indent("%s(tainted)\n",
+    skipping
+    ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
     if (skipping)
       debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
     "skipping: result is not used\n");
     }
+  }
 expand_level--;
 return yield->s;


@@ -8457,15 +8475,15 @@ if (opt_perl_startup != NULL)
}
#endif /* EXIM_PERL */

+/* Thie deliberately regards the input as untainted, so that it can be
+expanded; only reasonable since this is a test for string-expansions. */
+
 while (fgets(buffer, sizeof(buffer), stdin) != NULL)
   {
-  void *reset_point = store_get(0);
+  rmark reset_point = store_mark();
   uschar *yield = expand_string(buffer);
-  if (yield != NULL)
-    {
+  if (yield)
     printf("%s\n", yield);
-    store_reset(reset_point);
-    }
   else
     {
     if (f.search_find_defer) printf("search_find deferred\n");
@@ -8473,6 +8491,7 @@ while (fgets(buffer, sizeof(buffer), stdin) != NULL)
     if (f.expand_string_forcedfail) printf("Forced failure\n");
     printf("\n");
     }
+  store_reset(reset_point);
   }


search_tidyup();
diff --git a/src/src/filter.c b/src/src/filter.c
index a16416c..3da6167 100644
--- a/src/src/filter.c
+++ b/src/src/filter.c
@@ -435,7 +435,7 @@ for (;;)

   if (*ptr == 0)
     {
-    *error_pointer = string_sprintf("\"then\" missing at end of filter file");
+    *error_pointer = US"\"then\" missing at end of filter file";
     break;
     }


@@ -497,7 +497,7 @@ for (;;)

     /* Build a condition block from the specific word. */


-    c = store_get(sizeof(condition_block));
+    c = store_get(sizeof(condition_block), FALSE);
     c->left.u = c->right.u = NULL;
     c->testfor = testfor;
     testfor = TRUE;
@@ -527,7 +527,7 @@ for (;;)
           }
         ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
         if (*error_pointer) break;
-        aa = store_get(sizeof(string_item));
+        aa = store_get(sizeof(string_item), FALSE);
         aa->text = string_copy(buffer);
         aa->next = c->left.a;
         c->left.a = aa;
@@ -678,7 +678,7 @@ for (;;)


     else if (Ustrcmp(buffer, "and") == 0)
       {
-      condition_block *andc = store_get(sizeof(condition_block));
+      condition_block *andc = store_get(sizeof(condition_block), FALSE);
       andc->parent = current_parent;
       andc->type = cond_and;
       andc->testfor = TRUE;
@@ -696,7 +696,7 @@ for (;;)


     else if (Ustrcmp(buffer, "or") == 0)
       {
-      condition_block *orc = store_get(sizeof(condition_block));
+      condition_block *orc = store_get(sizeof(condition_block), FALSE);
       condition_block *or_parent = NULL;


       if (current_parent)
@@ -856,12 +856,12 @@ white space here. */


 if (Ustrncmp(ptr, "if(", 3) == 0)
   {
-  Ustrcpy(buffer, "if");
+  Ustrcpy(buffer, US"if");
   ptr += 2;
   }
 else if (Ustrncmp(ptr, "elif(", 5) == 0)
   {
-  Ustrcpy(buffer, "elif");
+  Ustrcpy(buffer, US"elif");
   ptr += 4;
   }
 else
@@ -981,7 +981,7 @@ switch (command)
       if (command == logwrite_command)
         {
         int len = Ustrlen(buffer);
-        if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, "\n");
+        if (len == 0 || buffer[len-1] != '\n') Ustrcat(buffer, US"\n");
         }


       argument.u = string_copy(buffer);
@@ -1015,7 +1015,7 @@ switch (command)


     if (*error_pointer != NULL) yield = FALSE; else
       {
-      new = store_get(sizeof(filter_cmd) + sizeof(union argtypes));
+      new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), FALSE);
       new->next = NULL;
       **lastcmdptr = new;
       *lastcmdptr = &(new->next);
@@ -1099,7 +1099,7 @@ switch (command)
   /* Finish has no arguments; fmsg defaults to NULL */


case finish_command:
- new = store_get(sizeof(filter_cmd));
+ new = store_get(sizeof(filter_cmd), FALSE);
new->next = NULL;
**lastcmdptr = new;
*lastcmdptr = &(new->next);
@@ -1123,7 +1123,7 @@ switch (command)

/* Set up the command block for if */

-  new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes));
+  new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE);
   new->next = NULL;
   **lastcmdptr = new;
   *lastcmdptr = &(new->next);
@@ -1151,7 +1151,7 @@ switch (command)
     while (had_else_endif == had_elif)
       {
       filter_cmd *newnew =
-        store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes));
+        store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE);
       new->args[2].f = newnew;
       new = newnew;
       new->next = NULL;
@@ -1204,7 +1204,7 @@ switch (command)


   case mail_command:
   case vacation_command:
-  new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes));
+  new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), FALSE);
   new->next = NULL;
   new->command = command;
   new->seen = seen_force? seen_value : FALSE;
@@ -1516,7 +1516,7 @@ switch (c->type)
           (debug_selector & D_filter) != 0)
         {
         indent();
-        debug_printf("Extracted address %s\n", filter_thisaddress);
+        debug_printf_indent("Extracted address %s\n", filter_thisaddress);
         }
       yield = test_condition(c->right.c, FALSE);
       }
@@ -1590,9 +1590,9 @@ switch (c->type)
     if ((filter_test != FTEST_NONE && debug_selector != 0) ||
         (debug_selector & D_filter) != 0)
       {
-      debug_printf("Match expanded arguments:\n");
-      debug_printf("  Subject = %s\n", exp[0]);
-      debug_printf("  Pattern = %s\n", exp[1]);
+      debug_printf_indent("Match expanded arguments:\n");
+      debug_printf_indent("  Subject = %s\n", exp[0]);
+      debug_printf_indent("  Pattern = %s\n", exp[1]);
       }


     re = pcre_compile(CS exp[1],
@@ -1634,11 +1634,11 @@ if ((filter_test != FTEST_NONE && debug_selector != 0) ||
     (debug_selector & D_filter) != 0)
   {
   indent();
-  debug_printf("%sondition is %s: ",
+  debug_printf_indent("%sondition is %s: ",
     toplevel? "C" : "Sub-c",
     (yield == c->testfor)? "true" : "false");
   print_condition(c, TRUE);
-  debug_printf("\n");
+  debug_printf_indent("\n");
   }


return yield == c->testfor;
@@ -1674,7 +1674,7 @@ int mode;
address_item *addr;
BOOL condition_value;

-while (commands != NULL)
+while (commands)
   {
   int ff_ret;
   uschar *fmsg, *ff_name;
@@ -1688,21 +1688,16 @@ while (commands != NULL)
   for (i = 0; i < (command_exparg_count[commands->command] & 15); i++)
     {
     uschar *ss = commands->args[i].u;
-    if (ss == NULL)
-      {
+    if (!ss)
       expargs[i] = NULL;
-      }
     else
-      {
-      expargs[i] = expand_string(ss);
-      if (expargs[i] == NULL)
+      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;
         }
-      }
     }


   /* Now switch for each command, setting the "delivered" flag if any of them
@@ -1713,640 +1708,640 @@ while (commands != NULL)
   switch(commands->command)
     {
     case add_command:
-    for (i = 0; i < 2; i++)
-      {
-      uschar *ss = expargs[i];
-      uschar *end;
+      for (i = 0; i < 2; i++)
+    {
+    uschar *ss = expargs[i];
+    uschar *end;


-      if (i == 1 && (*ss++ != 'n' || ss[1] != 0))
-        {
-        *error_pointer = string_sprintf("unknown variable \"%s\" in \"add\" "
-          "command", expargs[i]);
-        return FF_ERROR;
-        }
+    if (i == 1 && (*ss++ != 'n' || ss[1] != 0))
+      {
+      *error_pointer = string_sprintf("unknown variable \"%s\" in \"add\" "
+        "command", expargs[i]);
+      return FF_ERROR;
+      }


-      /* Allow for "--" at the start of the value (from -$n0) for example */
-      if (i == 0) while (ss[0] == '-' && ss[1] == '-') ss += 2;
+    /* Allow for "--" at the start of the value (from -$n0) for example */
+    if (i == 0) while (ss[0] == '-' && ss[1] == '-') ss += 2;


-      n[i] = (int)Ustrtol(ss, &end, 0);
-      if (*end != 0)
-        {
-        *error_pointer = string_sprintf("malformed number \"%s\" in \"add\" "
-          "command", ss);
-        return FF_ERROR;
-        }
-      }
+    n[i] = (int)Ustrtol(ss, &end, 0);
+    if (*end != 0)
+      {
+      *error_pointer = string_sprintf("malformed number \"%s\" in \"add\" "
+        "command", ss);
+      return FF_ERROR;
+      }
+    }


-    filter_n[n[1]] += n[0];
-    if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]);
-    break;
+      filter_n[n[1]] += n[0];
+      if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]);
+      break;


-    /* A deliver command's argument must be a valid address. Its optional
-    second argument (system filter only) must also be a valid address. */
+      /* A deliver command's argument must be a valid address. Its optional
+      second argument (system filter only) must also be a valid address. */


     case deliver_command:
-    for (i = 0; i < 2; i++)
-      {
-      s = expargs[i];
-      if (s != NULL)
-        {
-        int start, end, domain;
-        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);
-        else
-          {
-          *error_pointer = string_sprintf("malformed address \"%s\" in "
-            "filter file: %s", s, error);
-          return FF_ERROR;
-          }
-        }
-      }
+      for (i = 0; i < 2; i++)
+    {
+    s = expargs[i];
+    if (s != NULL)
+      {
+      int start, end, domain;
+      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);
+      else
+        {
+        *error_pointer = string_sprintf("malformed address \"%s\" in "
+          "filter file: %s", s, error);
+        return FF_ERROR;
+        }
+      }
+    }


-    /* Stick the errors address into a simple variable, as it will
-    be referenced a few times. Check that the caller is permitted to
-    specify it. */
+      /* Stick the errors address into a simple variable, as it will
+      be referenced a few times. Check that the caller is permitted to
+      specify it. */


-    s = expargs[1];
+      s = expargs[1];


-    if (s != NULL && !f.system_filtering)
-      {
-      uschar *ownaddress = expand_string(US"$local_part@$domain");
-      if (strcmpic(ownaddress, s) != 0)
-        {
-        *error_pointer = US"errors_to must point to the caller's address";
-        return FF_ERROR;
-        }
-      }
+      if (s != NULL && !f.system_filtering)
+    {
+    uschar *ownaddress = expand_string(US"$local_part@$domain");
+    if (strcmpic(ownaddress, s) != 0)
+      {
+      *error_pointer = US"errors_to must point to the caller's address";
+      return FF_ERROR;
+      }
+    }


-    /* Test case: report what would happen */
+      /* Test case: report what would happen */


-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      printf("%seliver message to: %s%s%s%s\n",
-        (commands->seen)? "D" : "Unseen d",
-        expargs[0],
-        commands->noerror? " (noerror)" : "",
-        (s != NULL)? " errors_to " : "",
-        (s != NULL)? s : US"");
-      }
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    printf("%seliver message to: %s%s%s%s\n",
+      (commands->seen)? "D" : "Unseen d",
+      expargs[0],
+      commands->noerror? " (noerror)" : "",
+      (s != NULL)? " errors_to " : "",
+      (s != NULL)? s : US"");
+    }


-    /* Real case. */
+      /* Real case. */


-    else
-      {
-      DEBUG(D_filter) debug_printf("Filter: %sdeliver message to: %s%s%s%s\n",
-        (commands->seen)? "" : "unseen ",
-        expargs[0],
-        commands->noerror? " (noerror)" : "",
-        (s != NULL)? " errors_to " : "",
-        (s != NULL)? s : US"");
-
-      /* Create the new address and add it to the chain, setting the
-      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 */
-      if (commands->noerror) addr->prop.ignore_error = TRUE;
-      addr->next = *generated;
-      *generated = addr;
-      }
-    break;
+      else
+    {
+    DEBUG(D_filter) debug_printf_indent("Filter: %sdeliver message to: %s%s%s%s\n",
+      (commands->seen)? "" : "unseen ",
+      expargs[0],
+      commands->noerror? " (noerror)" : "",
+      (s != NULL)? " errors_to " : "",
+      (s != NULL)? s : US"");
+
+    /* Create the new address and add it to the chain, setting the
+    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 */
+    if (commands->noerror) addr->prop.ignore_error = TRUE;
+    addr->next = *generated;
+    *generated = addr;
+    }
+      break;


     case save_command:
-    s = expargs[0];
-    mode = commands->args[1].i;
+      s = expargs[0];
+      mode = commands->args[1].i;


-    /* Test case: report what would happen */
+      /* Test case: report what would happen */


-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      if (mode < 0)
-        printf("%save message to: %s%s\n", (commands->seen)?
-          "S" : "Unseen s", s, commands->noerror? " (noerror)" : "");
-      else
-        printf("%save message to: %s %04o%s\n", (commands->seen)?
-          "S" : "Unseen s", s, mode, commands->noerror? " (noerror)" : "");
-      }
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    if (mode < 0)
+      printf("%save message to: %s%s\n", (commands->seen)?
+        "S" : "Unseen s", s, commands->noerror? " (noerror)" : "");
+    else
+      printf("%save message to: %s %04o%s\n", (commands->seen)?
+        "S" : "Unseen s", s, mode, commands->noerror? " (noerror)" : "");
+    }


-    /* Real case: Ensure save argument starts with / if there is a home
-    directory to prepend. */
+      /* Real case: Ensure save argument starts with / if there is a home
+      directory to prepend. */


-    else
-      {
-      if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 &&
-          deliver_home != NULL && deliver_home[0] != 0)
-        s = string_sprintf("%s/%s", deliver_home, s);
-      DEBUG(D_filter) debug_printf("Filter: %ssave message to: %s%s\n",
-        (commands->seen)? "" : "unseen ", s,
-        commands->noerror? " (noerror)" : "");
-
-      /* Create the new address and add it to the chain, setting the
-      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 */
-      setflag(addr, af_pfr);
-      setflag(addr, af_file);
-      if (commands->noerror) addr->prop.ignore_error = TRUE;
-      addr->mode = mode;
-      addr->next = *generated;
-      *generated = addr;
-      }
-    break;
+      else
+    {
+    if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 &&
+        deliver_home != NULL && deliver_home[0] != 0)
+      s = string_sprintf("%s/%s", deliver_home, s);
+    DEBUG(D_filter) debug_printf_indent("Filter: %ssave message to: %s%s\n",
+      (commands->seen)? "" : "unseen ", s,
+      commands->noerror? " (noerror)" : "");
+
+    /* Create the new address and add it to the chain, setting the
+    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 */
+    setflag(addr, af_pfr);
+    setflag(addr, af_file);
+    if (commands->noerror) addr->prop.ignore_error = TRUE;
+    addr->mode = mode;
+    addr->next = *generated;
+    *generated = addr;
+    }
+      break;


     case pipe_command:
-    s = string_copy(commands->args[0].u);
-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      printf("%sipe message to: %s%s\n", (commands->seen)?
-        "P" : "Unseen p", s, commands->noerror? " (noerror)" : "");
-      }
-    else /* Ensure pipe command starts with | */
-      {
-      DEBUG(D_filter) debug_printf("Filter: %spipe message to: %s%s\n",
-        (commands->seen)? "" : "unseen ", s,
-        commands->noerror? " (noerror)" : "");
-      if (s[0] != '|') s = string_sprintf("|%s", s);
-
-      /* Create the new address and add it to the chain, setting the
-      af_ignore_error flag if necessary. Set the af_expand_pipe flag so that
-      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 */
-      setflag(addr, af_pfr);
-      setflag(addr, af_expand_pipe);
-      if (commands->noerror) addr->prop.ignore_error = TRUE;
-      addr->next = *generated;
-      *generated = addr;
-
-      /* If there are any numeric variables in existence (e.g. after a regex
-      condition), or if $thisaddress is set, take a copy for use in the
-      expansion. Note that we can't pass NULL for filter_thisaddress, because
-      NULL terminates the list. */
-
-      if (expand_nmax >= 0 || filter_thisaddress != NULL)
-        {
-        int i;
-        int ecount = (expand_nmax >= 0)? expand_nmax : -1;
-        uschar **ss = store_get(sizeof(uschar *) * (ecount + 3));
-        addr->pipe_expandn = ss;
-        if (filter_thisaddress == NULL) filter_thisaddress = US"";
-        *ss++ = string_copy(filter_thisaddress);
-        for (i = 0; i <= expand_nmax; i++)
-          *ss++ = string_copyn(expand_nstring[i], expand_nlength[i]);
-        *ss = NULL;
-        }
-      }
-    break;
+      s = string_copy(commands->args[0].u);
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    printf("%sipe message to: %s%s\n", (commands->seen)?
+      "P" : "Unseen p", s, commands->noerror? " (noerror)" : "");
+    }
+      else /* Ensure pipe command starts with | */
+    {
+    DEBUG(D_filter) debug_printf_indent("Filter: %spipe message to: %s%s\n",
+      commands->seen ? "" : "unseen ", s,
+      commands->noerror ? " (noerror)" : "");
+    if (s[0] != '|') s = string_sprintf("|%s", s);


-    /* Set up the file name and mode, and close any previously open
-    file. */
+    /* Create the new address and add it to the chain, setting the
+    af_ignore_error flag if necessary. Set the af_expand_pipe flag so that
+    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 */
+    setflag(addr, af_pfr);
+    setflag(addr, af_expand_pipe);
+    if (commands->noerror) addr->prop.ignore_error = TRUE;
+    addr->next = *generated;
+    *generated = addr;
+
+    /* If there are any numeric variables in existence (e.g. after a regex
+    condition), or if $thisaddress is set, take a copy for use in the
+    expansion. Note that we can't pass NULL for filter_thisaddress, because
+    NULL terminates the list. */
+
+    if (expand_nmax >= 0 || filter_thisaddress != NULL)
+      {
+      int ecount = expand_nmax >= 0 ? expand_nmax : -1;
+      uschar **ss = store_get(sizeof(uschar *) * (ecount + 3), FALSE);
+
+      addr->pipe_expandn = ss;
+      if (!filter_thisaddress) filter_thisaddress = US"";
+      *ss++ = string_copy(filter_thisaddress);
+      for (int i = 0; i <= expand_nmax; i++)
+        *ss++ = string_copyn(expand_nstring[i], expand_nlength[i]);
+      *ss = NULL;
+      }
+    }
+      break;
+
+      /* Set up the file name and mode, and close any previously open
+      file. */


     case logfile_command:
-    log_mode = commands->args[1].i;
-    if (log_mode == -1) log_mode = 0600;
-    if (log_fd >= 0)
-      {
-      (void)close(log_fd);
-      log_fd = -1;
-      }
-    log_filename = expargs[0];
-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename);
-      }
-    break;
+      log_mode = commands->args[1].i;
+      if (log_mode == -1) log_mode = 0600;
+      if (log_fd >= 0)
+    {
+    (void)close(log_fd);
+    log_fd = -1;
+    }
+      log_filename = expargs[0];
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename);
+    }
+      break;


     case logwrite_command:
-    s = expargs[0];
+      s = expargs[0];


-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L",
-        string_printing(s));
-      }
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L",
+      string_printing(s));
+    }


-    /* Attempt to write to a log file only if configured as permissible.
-    Logging may be forcibly skipped for verifying or testing. */
+      /* Attempt to write to a log file only if configured as permissible.
+      Logging may be forcibly skipped for verifying or testing. */


-    else if ((filter_options & RDO_LOG) != 0)   /* Locked out */
-      {
-      DEBUG(D_filter)
-        debug_printf("filter log command aborted: euid=%ld\n",
-        (long int)geteuid());
-      *error_pointer = US"logwrite command forbidden";
-      return FF_ERROR;
-      }
-    else if ((filter_options & RDO_REALLOG) != 0)
-      {
-      int len;
-      DEBUG(D_filter) debug_printf("writing filter log as euid %ld\n",
-        (long int)geteuid());
-      if (log_fd < 0)
-        {
-        if (log_filename == NULL)
-          {
-          *error_pointer = US"attempt to obey \"logwrite\" command "
-            "without a previous \"logfile\"";
-          return FF_ERROR;
-          }
-        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\"",
-            log_filename);
-          return FF_ERROR;
-          }
-        }
-      len = Ustrlen(s);
-      if (write(log_fd, s, len) != len)
-        {
-        *error_pointer = string_sprintf("write error on file \"%s\": %s",
-          log_filename, strerror(errno));
-        return FF_ERROR;
-        }
-      }
-    else
-      {
-      DEBUG(D_filter) debug_printf("skipping logwrite (verifying or testing)\n");
-      }
-    break;
+      else if ((filter_options & RDO_LOG) != 0)   /* Locked out */
+    {
+    DEBUG(D_filter)
+      debug_printf_indent("filter log command aborted: euid=%ld\n",
+      (long int)geteuid());
+    *error_pointer = US"logwrite command forbidden";
+    return FF_ERROR;
+    }
+      else if ((filter_options & RDO_REALLOG) != 0)
+    {
+    int len;
+    DEBUG(D_filter) debug_printf_indent("writing filter log as euid %ld\n",
+      (long int)geteuid());
+    if (log_fd < 0)
+      {
+      if (log_filename == NULL)
+        {
+        *error_pointer = US"attempt to obey \"logwrite\" command "
+          "without a previous \"logfile\"";
+        return FF_ERROR;
+        }
+      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\"",
+          log_filename);
+        return FF_ERROR;
+        }
+      }
+    len = Ustrlen(s);
+    if (write(log_fd, s, len) != len)
+      {
+      *error_pointer = string_sprintf("write error on file \"%s\": %s",
+        log_filename, strerror(errno));
+      return FF_ERROR;
+      }
+    }
+      else
+    {
+    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
-    command is rejected at parse time otherwise. However "headers charset" is
-    always permitted. */
+      /* Header addition and removal is available only in the system filter. The
+      command is rejected at parse time otherwise. However "headers charset" is
+      always permitted. */


     case headers_command:
-      {
-      int subtype = commands->args[1].i;
-      s = expargs[0];
+    {
+    int subtype = commands->args[1].i;
+    s = expargs[0];


-      if (filter_test != FTEST_NONE)
-        printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" :
-          (subtype == FALSE)? "remove" : "charset", string_printing(s));
+    if (filter_test != FTEST_NONE)
+      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)
-          {
-          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;
-          }
-        }
+    if (subtype == TRUE)
+      {
+      while (isspace(*s)) s++;
+      if (s[0] != 0)
+        {
+        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;
+        }
+      }


-      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)
-          header_remove(0, ss);
-        }
+    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)
+        header_remove(0, ss);
+      }


-      /* This setting lasts only while the filter is running; on exit, the
-      variable is reset to the previous value. */
+    /* This setting lasts only while the filter is running; on exit, the
+    variable is reset to the previous value. */


-      else headers_charset = s;
-      }
-    break;
+    else headers_charset = s;
+    }
+      break;


-    /* Defer, freeze, and fail are available only when explicitly permitted.
-    These commands are rejected at parse time otherwise. The message can get
-    very long by the inclusion of message headers; truncate if it is, and also
-    ensure printing characters so as not to mess up log files. */
+      /* Defer, freeze, and fail are available only when explicitly permitted.
+      These commands are rejected at parse time otherwise. The message can get
+      very long by the inclusion of message headers; truncate if it is, and also
+      ensure printing characters so as not to mess up log files. */


     case defer_command:
-    ff_name = US"defer";
-    ff_ret = FF_DEFER;
-    goto DEFERFREEZEFAIL;
+      ff_name = US"defer";
+      ff_ret = FF_DEFER;
+      goto DEFERFREEZEFAIL;


     case fail_command:
-    ff_name = US"fail";
-    ff_ret = FF_FAIL;
-    goto DEFERFREEZEFAIL;
+      ff_name = US"fail";
+      ff_ret = FF_FAIL;
+      goto DEFERFREEZEFAIL;


     case freeze_command:
-    ff_name = US"freeze";
-    ff_ret = FF_FREEZE;
+      ff_name = US"freeze";
+      ff_ret = FF_FREEZE;


-    DEFERFREEZEFAIL:
-    fmsg = expargs[0];
-    if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)");
-    fmsg = US string_printing(fmsg);
-    *error_pointer = fmsg;
+      DEFERFREEZEFAIL:
+      fmsg = expargs[0];
+      if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, US" ... (truncated)");
+      fmsg = US string_printing(fmsg);
+      *error_pointer = fmsg;


-    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("Filter: %s \"%s\"\n", ff_name, fmsg);
-    return ff_ret;
+      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);
+      return ff_ret;


     case finish_command:
-    if (filter_test != FTEST_NONE)
-      {
-      indent();
-      printf("%sinish\n", (commands->seen)? "Seen f" : "F");
-      }
-    else
-      {
-      DEBUG(D_filter) debug_printf("Filter: %sfinish\n",
-        (commands->seen)? " Seen " : "");
-      }
-    finish_obeyed = TRUE;
-    return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED;
+      if (filter_test != FTEST_NONE)
+    {
+    indent();
+    printf("%sinish\n", (commands->seen)? "Seen f" : "F");
+    }
+      else
+    {
+    DEBUG(D_filter) debug_printf_indent("Filter: %sfinish\n",
+      (commands->seen)? " Seen " : "");
+    }
+      finish_obeyed = TRUE;
+      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
-        {
-        output_indent += 2;
-        ok = interpret_commands(commands->args[condition_value? 1:2].f,
-          generated);
-        output_indent -= 2;
-        }
-      filter_thisaddress = save_address;
-      if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED))
-        return ok;
-      }
-    break;
+    {
+    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
+      {
+      output_indent += 2;
+      ok = interpret_commands(commands->args[condition_value? 1:2].f,
+        generated);
+      output_indent -= 2;
+      }
+    filter_thisaddress = save_address;
+    if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED))
+      return ok;
+    }
+      break;



-    /* To try to catch runaway loops, do not generate mail if the
-    return path is unset or if a non-trusted user supplied -f <>
-    as the return path. */
+      /* To try to catch runaway loops, do not generate mail if the
+      return path is unset or if a non-trusted user supplied -f <>
+      as the return path. */


     case mail_command:
     case vacation_command:
-      if (return_path == NULL || return_path[0] == 0)
-    {
-    if (filter_test != FTEST_NONE)
-      printf("%s command ignored because return_path is empty\n",
-        command_list[commands->command]);
-    else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
-      "is empty\n", command_list[commands->command]);
-    break;
-    }
+    if (return_path == NULL || return_path[0] == 0)
+      {
+      if (filter_test != FTEST_NONE)
+        printf("%s command ignored because return_path is empty\n",
+          command_list[commands->command]);
+      else DEBUG(D_filter) debug_printf_indent("%s command ignored because return_path "
+        "is empty\n", command_list[commands->command]);
+      break;
+      }


-      /* Check the contents of the strings. The type of string can be deduced
-      from the value of i.
+    /* Check the contents of the strings. The type of string can be deduced
+    from the value of i.


-      . If i is equal to mailarg_index_text it's a text string for the body,
-    where anything goes.
+    . If i is equal to mailarg_index_text it's a text string for the body,
+      where anything goes.


-      . If i is > mailarg_index_text, we are dealing with a file name, which
-    cannot contain non-printing characters.
+    . If i is > mailarg_index_text, we are dealing with a file name, which
+      cannot contain non-printing characters.


-      . If i is less than mailarg_index_headers we are dealing with something
-    that will go in a single message header line, where newlines must be
-    followed by white space.
+    . If i is less than mailarg_index_headers we are dealing with something
+      that will go in a single message header line, where newlines must be
+      followed by white space.


-      . If i is equal to mailarg_index_headers, we have a string that contains
-    one or more headers. Newlines that are not followed by white space must
-    be followed by a header name.
-      */
+    . If i is equal to mailarg_index_headers, we have a string that contains
+      one or more headers. Newlines that are not followed by white space must
+      be followed by a header name.
+    */


-      for (i = 0; i < MAILARGS_STRING_COUNT; i++)
-    {
-    uschar *p;
-    uschar *s = expargs[i];
+    for (i = 0; i < MAILARGS_STRING_COUNT; i++)
+      {
+      uschar *p;
+      uschar *s = expargs[i];


-    if (s == NULL) continue;
+      if (s == NULL) continue;


-    if (i != mailarg_index_text) for (p = s; *p != 0; p++)
-      {
-      int c = *p;
-      if (i > mailarg_index_text)
+      if (i != mailarg_index_text) for (p = s; *p != 0; p++)
         {
-        if (!mac_isprint(c))
+        int c = *p;
+        if (i > mailarg_index_text)
           {
-          *error_pointer = string_sprintf("non-printing character in \"%s\" "
-        "in %s command", string_printing(s),
-        command_list[commands->command]);
-          return FF_ERROR;
+          if (!mac_isprint(c))
+        {
+        *error_pointer = string_sprintf("non-printing character in \"%s\" "
+          "in %s command", string_printing(s),
+          command_list[commands->command]);
+        return FF_ERROR;
+        }
           }
-        }


-      /* i < mailarg_index_text */
+        /* i < mailarg_index_text */


-      else if (c == '\n' && !isspace(p[1]))
-        {
-        if (i < mailarg_index_headers)
+        else if (c == '\n' && !isspace(p[1]))
           {
-          *error_pointer = string_sprintf("\\n not followed by space in "
-        "\"%.1024s\" in %s command", string_printing(s),
-        command_list[commands->command]);
-          return FF_ERROR;
-          }
+          if (i < mailarg_index_headers)
+        {
+        *error_pointer = string_sprintf("\\n not followed by space in "
+          "\"%.1024s\" in %s command", string_printing(s),
+          command_list[commands->command]);
+        return FF_ERROR;
+        }


-        /* Check for the start of a new header line within the string */
+          /* Check for the start of a new header line within the string */


-        else
-          {
-          uschar *pp;
-          for (pp = p + 1;; pp++)
+          else
         {
-        c = *pp;
-        if (c == ':' && pp != p + 1) break;
-        if (c == 0 || c == ':' || isspace(*pp))
+        uschar *pp;
+        for (pp = p + 1;; pp++)
           {
-          *error_pointer = string_sprintf("\\n not followed by space or "
-            "valid header name in \"%.1024s\" in %s command",
-            string_printing(s), command_list[commands->command]);
-          return FF_ERROR;
+          c = *pp;
+          if (c == ':' && pp != p + 1) break;
+          if (c == 0 || c == ':' || isspace(*pp))
+            {
+            *error_pointer = string_sprintf("\\n not followed by space or "
+              "valid header name in \"%.1024s\" in %s command",
+              string_printing(s), command_list[commands->command]);
+            return FF_ERROR;
+            }
           }
+        p = pp;
         }
-          p = pp;
           }
-        }
-      }       /* Loop to scan the string */
-
-    /* The string is OK */
+        }       /* Loop to scan the string */


-    commands->args[i].u = s;
-    }
+      /* The string is OK */


-      /* Proceed with mail or vacation command */
-
-      if (filter_test != FTEST_NONE)
-    {
-    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>",
-      commands->command == vacation_command ? " (vacation)" : "",
-      commands->noerror ? " (noerror)" : "");
-    for (i = 1; i < MAILARGS_STRING_COUNT; i++)
-      {
-      uschar *arg = commands->args[i].u;
-      if (arg)
-        {
-        int len = Ustrlen(mailargs[i]);
-        int indent = (debug_selector != 0)? output_indent : 0;
-        while (len++ < 7 + indent) printf(" ");
-        printf("%s: %s%s\n", mailargs[i], string_printing(arg),
-          (commands->args[mailarg_index_expand].u != NULL &&
-        Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
-        }
+      commands->args[i].u = s;
       }
-    if (commands->args[mailarg_index_return].u)
-      printf("Return original message\n");
-    }
-      else
-    {
-    uschar *tt;
-    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 = ' ';
+    /* Proceed with mail or vacation command */


-    DEBUG(D_filter)
+    if (filter_test != FTEST_NONE)
       {
-      debug_printf("Filter: %smail to: %s%s%s\n",
-        commands->seen ? "seen " : "",
-        to,
+      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>",
         commands->command == vacation_command ? " (vacation)" : "",
         commands->noerror ? " (noerror)" : "");
       for (i = 1; i < MAILARGS_STRING_COUNT; i++)
         {
         uschar *arg = commands->args[i].u;
-        if (arg != NULL)
+        if (arg)
           {
           int len = Ustrlen(mailargs[i]);
-          while (len++ < 15) debug_printf(" ");
-          debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg),
+          int indent = (debug_selector != 0)? output_indent : 0;
+          while (len++ < 7 + indent) printf(" ");
+          printf("%s: %s%s\n", mailargs[i], string_printing(arg),
         (commands->args[mailarg_index_expand].u != NULL &&
           Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
           }
         }
+      if (commands->args[mailarg_index_return].u)
+        printf("Return original message\n");
       }
-
-    /* Create the "address" for the autoreply. This is used only for logging,
-    as the actual recipients are extracted from the To: line by -t. We use the
-    same logic here to extract the working addresses (there may be more than
-    one). Just in case there are a vast number of addresses, stop when the
-    string gets too long. */
-
-    tt = to;
-    while (*tt != 0)
+    else
       {
-      uschar *ss = parse_find_address_end(tt, FALSE);
-      uschar *recipient, *errmess;
-      int start, end, domain;
-      int temp = *ss;
+      uschar *tt;
+      uschar *to = commands->args[mailarg_index_to].u;
+      gstring * log_addr = NULL;


-      *ss = 0;
-      recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
-        FALSE);
-      *ss = temp;
+      if (!to) to = expand_string(US"$reply_address");
+      while (isspace(*to)) to++;


-      /* Ignore empty addresses and errors; an error will occur later if
-      there's something really bad. */
+      for (tt = to; *tt != 0; tt++)     /* Get rid of newlines */
+        if (*tt == '\n') *tt = ' ';


-      if (recipient)
+      DEBUG(D_filter)
         {
-        log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1);
-        log_addr = string_cat (log_addr, recipient);
+        debug_printf_indent("Filter: %smail to: %s%s%s\n",
+          commands->seen ? "seen " : "",
+          to,
+          commands->command == vacation_command ? " (vacation)" : "",
+          commands->noerror ? " (noerror)" : "");
+        for (i = 1; i < MAILARGS_STRING_COUNT; i++)
+          {
+          uschar *arg = commands->args[i].u;
+          if (arg != NULL)
+        {
+        int len = Ustrlen(mailargs[i]);
+        while (len++ < 15) debug_printf_indent(" ");
+        debug_printf_indent("%s: %s%s\n", mailargs[i], string_printing(arg),
+          (commands->args[mailarg_index_expand].u != NULL &&
+            Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+        }
+          }
         }


-      /* Check size */
+      /* Create the "address" for the autoreply. This is used only for logging,
+      as the actual recipients are extracted from the To: line by -t. We use the
+      same logic here to extract the working addresses (there may be more than
+      one). Just in case there are a vast number of addresses, stop when the
+      string gets too long. */


-      if (log_addr && log_addr->ptr > 256)
+      tt = to;
+      while (*tt != 0)
         {
-        log_addr = string_catn(log_addr, US", ...", 5);
-        break;
-        }
+        uschar *ss = parse_find_address_end(tt, FALSE);
+        uschar *recipient, *errmess;
+        int start, end, domain;
+        int temp = *ss;


-      /* Move on past this address */
+        *ss = 0;
+        recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
+          FALSE);
+        *ss = temp;


-      tt = ss + (*ss ? 1 : 0);
-      while (isspace(*tt)) tt++;
-      }
+        /* Ignore empty addresses and errors; an error will occur later if
+        there's something really bad. */


-    if (log_addr)
-      addr = deliver_make_addr(string_from_gstring(log_addr), FALSE);
-    else
-      {
-      addr = deliver_make_addr(US ">**bad-reply**", FALSE);
-      setflag(addr, af_bad_reply);
-      }
+        if (recipient)
+          {
+          log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1);
+          log_addr = string_cat (log_addr, recipient);
+          }


-    setflag(addr, af_pfr);
-    if (commands->noerror) addr->prop.ignore_error = TRUE;
-    addr->next = *generated;
-    *generated = addr;
+        /* Check size */


-    addr->reply = store_get(sizeof(reply_item));
-    addr->reply->from = NULL;
-    addr->reply->to = string_copy(to);
-    addr->reply->file_expand =
-      commands->args[mailarg_index_expand].u != NULL;
-    addr->reply->expand_forbid = expand_forbid;
-    addr->reply->return_message =
-      commands->args[mailarg_index_return].u != NULL;
-    addr->reply->once_repeat = 0;
-
-    if (commands->args[mailarg_index_once_repeat].u != NULL)
-      {
-      addr->reply->once_repeat =
-        readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
-          FALSE);
-      if (addr->reply->once_repeat < 0)
+        if (log_addr && log_addr->ptr > 256)
+          {
+          log_addr = string_catn(log_addr, US", ...", 5);
+          break;
+          }
+
+        /* Move on past this address */
+
+        tt = ss + (*ss ? 1 : 0);
+        while (isspace(*tt)) tt++;
+        }
+
+      if (log_addr)
+        addr = deliver_make_addr(string_from_gstring(log_addr), FALSE);
+      else
         {
-        *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
-          "in mail or vacation command: %s",
-          commands->args[mailarg_index_once_repeat].u);
-        return FF_ERROR;
+        addr = deliver_make_addr(US ">**bad-reply**", FALSE);
+        setflag(addr, af_bad_reply);
         }
-      }


-    /* Set up all the remaining string arguments (those other than "to") */
+      setflag(addr, af_pfr);
+      if (commands->noerror) addr->prop.ignore_error = TRUE;
+      addr->next = *generated;
+      *generated = addr;
+
+      addr->reply = store_get(sizeof(reply_item), FALSE);
+      addr->reply->from = NULL;
+      addr->reply->to = string_copy(to);
+      addr->reply->file_expand =
+        commands->args[mailarg_index_expand].u != NULL;
+      addr->reply->expand_forbid = expand_forbid;
+      addr->reply->return_message =
+        commands->args[mailarg_index_return].u != NULL;
+      addr->reply->once_repeat = 0;
+
+      if (commands->args[mailarg_index_once_repeat].u != NULL)
+        {
+        addr->reply->once_repeat =
+          readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
+        FALSE);
+        if (addr->reply->once_repeat < 0)
+          {
+          *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
+        "in mail or vacation command: %s",
+        commands->args[mailarg_index_once_repeat].u);
+          return FF_ERROR;
+          }
+        }


-    for (i = 1; i < mailargs_string_passed; i++)
-      {
-      uschar *ss = commands->args[i].u;
-      *(USS((US addr->reply) + reply_offsets[i])) =
-        ss ? string_copy(ss) : NULL;
+      /* Set up all the remaining string arguments (those other than "to") */
+
+      for (i = 1; i < mailargs_string_passed; i++)
+        {
+        uschar *ss = commands->args[i].u;
+        *(USS((US addr->reply) + reply_offsets[i])) =
+          ss ? string_copy(ss) : NULL;
+        }
       }
-    }
-      break;
+    break;


     case testprint_command:
-      if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
-    {
-    const uschar *s = string_printing(expargs[0]);
-    if (filter_test == FTEST_NONE)
-      debug_printf("Filter: testprint: %s\n", s);
-    else
-      printf("Testprint: %s\n", s);
-    }
+    if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
+      {
+      const uschar *s = string_printing(expargs[0]);
+      if (filter_test == FTEST_NONE)
+        debug_printf_indent("Filter: testprint: %s\n", s);
+      else
+        printf("Testprint: %s\n", s);
+      }
     }


commands = commands->next;
@@ -2376,7 +2371,7 @@ filter_personal(string_item *aliases, BOOL scan_cc)
{
uschar *self, *self_from, *self_to;
uschar *psself = NULL, *psself_from = NULL, *psself_to = NULL;
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();
BOOL yield;
header_line *h;
int to_count = 2;
@@ -2391,7 +2386,7 @@ defined in RFC 2369. We also scan for "Auto-Submitted"; if it is found to
contain any value other than "no", the message is not personal (RFC 3834).
Previously the test was for "auto-". */

-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
{
uschar *s;
if (h->type == htype_old) continue;
@@ -2515,6 +2510,7 @@ filter_cmd *commands = NULL;
filter_cmd **lastcmdptr = &commands;

DEBUG(D_route) debug_printf("Filter: start of processing\n");
+acl_level++;

 /* Initialize "not in an if command", set the global flag that is always TRUE
 while filtering, and zero the variables. */
@@ -2554,35 +2550,35 @@ if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
   switch(yield)
     {
     case FF_DEFER:
-    s = US"Filtering ended by \"defer\".";
-    break;
+      s = US"Filtering ended by \"defer\".";
+      break;


     case FF_FREEZE:
-    s = US"Filtering ended by \"freeze\".";
-    break;
+      s = US"Filtering ended by \"freeze\".";
+      break;


     case FF_FAIL:
-    s = US"Filtering ended by \"fail\".";
-    break;
+      s = US"Filtering ended by \"fail\".";
+      break;


     case FF_DELIVERED:
-    s = US"Filtering set up at least one significant delivery "
-           "or other action.\n"
-           "No other deliveries will occur.";
-    break;
+      s = US"Filtering set up at least one significant delivery "
+         "or other action.\n"
+         "No other deliveries will occur.";
+      break;


     case FF_NOTDELIVERED:
-    s = US"Filtering did not set up a significant delivery.\n"
-           "Normal delivery will occur.";
-    break;
+      s = US"Filtering did not set up a significant delivery.\n"
+         "Normal delivery will occur.";
+      break;


     case FF_ERROR:
-    s = string_sprintf("Filter error: %s", *error);
-    break;
+      s = string_sprintf("Filter error: %s", *error);
+      break;
     }


   if (filter_test != FTEST_NONE) printf("%s\n", CS s);
-    else debug_printf("%s\n", s);
+    else debug_printf_indent("%s\n", s);
   }


/* Close the log file if it was opened, and kill off any numerical variables
@@ -2593,6 +2589,7 @@ expand_nmax = -1;
f.filter_running = FALSE;
headers_charset = save_headers_charset;

+acl_level--;
 DEBUG(D_route) debug_printf("Filter: end of processing\n");
 return yield;
 }
diff --git a/src/src/filtertest.c b/src/src/filtertest.c
index f3d3acc..f54cbef 100644
--- a/src/src/filtertest.c
+++ b/src/src/filtertest.c
@@ -112,7 +112,7 @@ if (body_len >= message_body_visible)
   int above = message_body_visible - below;
   if (above > 0)
     {
-    uschar *temp = store_get(below);
+    uschar *temp = store_get(below, TRUE);
     memcpy(temp, message_body_end, below);
     memmove(message_body_end, s+1, above);
     memcpy(message_body_end + above, temp, below);
@@ -178,7 +178,7 @@ if (fstat(fd, &statbuf) != 0)
   return FALSE;
   }


-filebuf = store_get(statbuf.st_size + 1);
+filebuf = store_get(statbuf.st_size + 1, is_tainted(filename));
rc = read(fd, filebuf, statbuf.st_size);
(void)close(fd);

diff --git a/src/src/functions.h b/src/src/functions.h
index 806ba75..bcf04c2 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -218,6 +218,7 @@ extern const uschar * exim_errstr(int);
 extern void    exim_exit(int, const uschar *) NORETURN;
 extern void    exim_nullstd(void);
 extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
+extern void    exim_underbar_exit(int);
 extern void    exim_wait_tick(struct timeval *, int);
 extern int     exp_bool(address_item *addr,
   uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue,
@@ -458,7 +459,7 @@ extern void    smtp_log_no_mail(void);
 extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
 extern void    smtp_proxy_tls(void *, uschar *, size_t, int *, int) NORETURN;
 extern BOOL    smtp_read_response(void *, uschar *, int, int, int);
-extern void    smtp_reset(void *);
+extern void   *smtp_reset(void *);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
 extern void    smtp_port_for_connect(host_item *, int);
@@ -485,6 +486,8 @@ extern int     stdin_getc(unsigned);
 extern int     stdin_feof(void);
 extern int     stdin_ferror(void);
 extern int     stdin_ungetc(int);
+
+extern void    store_exit(void);
 extern gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT;
 extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT;
 extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsigned) WARN_UNUSED_RESULT;
@@ -496,8 +499,6 @@ extern int     string_compare_by_pointer(const void *, const void *);
 extern uschar *string_copy_dnsdomain(uschar *);
 extern uschar *string_copy_malloc(const uschar *);
 extern uschar *string_dequote(const uschar **);
-extern gstring *string_fmt_append(gstring *, const char *, ...) ALMOST_PRINTF(2,3);
-extern BOOL    string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4);
 extern uschar *string_format_size(int, uschar *);
 extern int     string_interpret_escape(const uschar **);
 extern int     string_is_ip_address(const uschar *, int *);
@@ -505,7 +506,6 @@ extern int     string_is_ip_address(const uschar *, int *);
 extern BOOL    string_is_utf8(const uschar *);
 #endif
 extern uschar *string_nextinlist(const uschar **, int *, uschar *, int);
-extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3);
 extern const uschar *string_printing2(const uschar *, BOOL);
 extern uschar *string_split_message(uschar *);
 extern uschar *string_timediff(struct timeval *);
@@ -518,7 +518,23 @@ extern uschar *string_domain_utf8_to_alabel(const uschar *, uschar **);
 extern uschar *string_localpart_alabel_to_utf8(const uschar *, uschar **);
 extern uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **);
 #endif
-extern gstring *string_vformat(gstring *, BOOL, const char *, va_list);
+
+#define string_format(buf, siz, fmt, ...) \
+    string_format_trc(buf, siz, US __FUNCTION__, __LINE__, fmt, __VA_ARGS__)
+extern BOOL    string_format_trc(uschar *, int, const uschar *, unsigned,
+            const char *, ...) ALMOST_PRINTF(5,6);
+
+#define string_vformat(g, flgs, fmt, ap) \
+    string_vformat_trc(g, US __FUNCTION__, __LINE__, \
+             STRING_SPRINTF_BUFFER_SIZE, flgs, fmt, ap)
+extern gstring *string_vformat_trc(gstring *, const uschar *, unsigned,
+            unsigned, unsigned, const char *, va_list);
+
+#define string_open_failed(eno, fmt, ...) \
+    string_open_failed_trc(eno, US __FUNCTION__, __LINE__, fmt, __VA_ARGS__)
+extern uschar *string_open_failed_trc(int, const uschar *, unsigned,
+            const char *, ...) PRINTF_FUNCTION(4,5);
+
 extern int     strcmpic(const uschar *, const uschar *);
 extern int     strncmpic(const uschar *, const uschar *, int);
 extern uschar *strstric(uschar *, uschar *, BOOL);
@@ -623,6 +639,7 @@ return chown(CCS name, owner, group)
 /******************************************************************************/
 /* String functions */


+#if !defined(MACRO_PREDEF)
 /*************************************************
 *            Copy and save string                *
 *************************************************/
@@ -630,16 +647,24 @@ return chown(CCS name, owner, group)
 /* This function assumes that memcpy() is faster than strcpy().
 */


-#if !defined(MACRO_PREDEF)
static inline uschar *
-string_copy(const uschar *s)
+string_copy_taint_trc(const uschar *s, BOOL tainted, const char * func, int line)
{
int len = Ustrlen(s) + 1;
-uschar *ss = store_get(len);
+uschar *ss = store_get_3(len, tainted, func, line);
memcpy(ss, s, len);
return ss;
}

+#define string_copy_taint(s, tainted) \
+    string_copy_taint_trc((s), tainted, __FUNCTION__, __LINE__)
+
+static inline uschar *
+string_copy(const uschar * s)
+{
+return string_copy_taint((s), is_tainted(s));
+}
+


 /*************************************************
 *       Copy, lowercase and save string          *
@@ -653,7 +678,7 @@ Returns:  copy of string in new store, with letters lowercased
 static inline uschar *
 string_copylc(const uschar *s)
 {
-uschar *ss = store_get(Ustrlen(s) + 1);
+uschar *ss = store_get(Ustrlen(s) + 1, is_tainted(s));
 uschar *p = ss;
 while (*s != 0) *p++ = tolower(*s++);
 *p = 0;
@@ -681,7 +706,7 @@ This is an API for local_scan hence not static.
 static inline uschar *
 string_copyn(const uschar *s, int n)
 {
-uschar *ss = store_get(n + 1);
+uschar *ss = store_get(n + 1, is_tainted(s));
 Ustrncpy(ss, s, n);
 ss[n] = 0;
 return ss;
@@ -704,7 +729,7 @@ Returns:    copy of string in new store, with letters lowercased
 static inline uschar *
 string_copynlc(uschar *s, int n)
 {
-uschar *ss = store_get(n + 1);
+uschar *ss = store_get(n + 1, is_tainted(s));
 uschar *p = ss;
 while (n-- > 0) *p++ = tolower(*s++);
 *p = 0;
@@ -712,21 +737,73 @@ return ss;
 }



+/*************************************************
+*     Copy and save string in longterm store     *
+*************************************************/
+
+/* This function assumes that memcpy() is faster than strcpy().
+
+Argument: string to copy
+Returns:  copy of string in new store
+*/
+
+static inline uschar *
+string_copy_perm(const uschar *s, BOOL force_taint)
+{
+int old_pool = store_pool;
+int len = Ustrlen(s) + 1;
+uschar *ss;
+
+store_pool = POOL_PERM;
+ss = store_get(len, force_taint || is_tainted(s));
+memcpy(ss, s, len);
+store_pool = old_pool;
+return ss;
+}
+
+
+
+/* sprintf into a buffer, taint-unchecked */
+
+static inline void
+string_format_nt(uschar * buf, int siz, const char * fmt, ...)
+{
+gstring gs = { .size = siz, .ptr = 0, .s = buf };
+va_list ap;
+va_start(ap, fmt);
+(void) string_vformat(&gs, SVFMT_TAINT_NOCHK, fmt, ap);
+va_end(ap);
+}
+
+
+
 /******************************************************************************/
 /* Growable-string functions */


-/* Create a growable-string with some preassigned space, in untainted memory */
+/* Create a growable-string with some preassigned space */
+
+#define string_get_tainted(size, tainted) \
+    string_get_tainted_trc((size), (tainted), __FUNCTION__, __LINE__)


static inline gstring *
-string_get(unsigned size)
+string_get_tainted_trc(unsigned size, BOOL tainted, const char * func, unsigned line)
{
-gstring * g = store_get(sizeof(gstring) + size);
+gstring * g = store_get_3(sizeof(gstring) + size, tainted, func, line);
g->size = size;
g->ptr = 0;
g->s = US(g + 1);
return g;
}

+#define string_get(size) \
+    string_get_trc((size), __FUNCTION__, __LINE__)
+
+static inline gstring *
+string_get_trc(unsigned size, const char * func, unsigned line)
+{
+return string_get_tainted_trc(size, FALSE, func, line);
+}
+
 /* NUL-terminate the C string in the growable-string, and return it. */


static inline uschar *
@@ -737,10 +814,37 @@ g->s[g->ptr] = '\0';
return g->s;
}

+
+#define gstring_release_unused(g) \
+    gstring_release_unused_trc(g, __FUNCTION__, __LINE__)
+
 static inline void
-gstring_release_unused(gstring * g)
+gstring_release_unused_trc(gstring * g, const char * file, unsigned line)
 {
-if (g) store_reset(g->s + (g->size = g->ptr + 1));
+if (g) store_release_above_3(g->s + (g->size = g->ptr + 1), file, line);
+}
+
+
+/* sprintf-append to a growable-string */
+
+#define string_fmt_append(g, fmt, ...) \
+    string_fmt_append_f_trc(g, US __FUNCTION__, __LINE__, \
+    SVFMT_EXTEND|SVFMT_REBUFFER, fmt, __VA_ARGS__)
+
+#define string_fmt_append_f(g, flgs, fmt, ...) \
+    string_fmt_append_f_trc(g, US __FUNCTION__, __LINE__, \
+    flgs,         fmt, __VA_ARGS__)
+
+static inline gstring *
+string_fmt_append_f_trc(gstring * g, const uschar * func, unsigned line,
+  unsigned flags, const char *format, ...)
+{
+va_list ap;
+va_start(ap, format);
+g = string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
+            flags, format, ap);
+va_end(ap);
+return g;
 }


 /******************************************************************************/
diff --git a/src/src/globals.c b/src/src/globals.c
index 742584e..15fb089 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -1169,7 +1169,7 @@ uschar *pipe_connect_advertise_hosts = US"*";
 #endif
 uschar *pipelining_advertise_hosts = US"*";
 uschar *primary_hostname       = NULL;
-uschar  process_info[PROCESS_INFO_SIZE];
+uschar *process_info;
 int     process_info_len       = 0;
 uschar *process_log_path       = NULL;


diff --git a/src/src/globals.h b/src/src/globals.h
index 80e1764..c226004 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -757,7 +757,7 @@ extern BOOL    prdr_requested;         /* Connecting mail server wants PRDR */
 extern BOOL    preserve_message_logs;  /* Save msglog files */
 extern uschar *primary_hostname;       /* Primary name of this computer */
 extern BOOL    print_topbitchars;      /* Topbit chars are printing chars */
-extern uschar  process_info[];         /* For SIGUSR1 output */
+extern uschar *process_info;           /* For SIGUSR1 output */
 extern int     process_info_len;
 extern uschar *process_log_path;       /* Alternate path */
 extern BOOL    prod_requires_admin;    /* TRUE if prodding requires admin */
diff --git a/src/src/hash.c b/src/src/hash.c
index 1bdeaef..f1a6c40 100644
--- a/src/src/hash.c
+++ b/src/src/hash.c
@@ -84,7 +84,8 @@ switch (h->method)
 void
 exim_sha_finish(hctx * h, blob * b)
 {
-b->data = store_get(b->len = h->hashlen);
+/* Hashing is sufficient to purify any tainted input */
+b->data = store_get(b->len = h->hashlen, FALSE);
 switch (h->method)
   {
   case HASH_SHA1:     SHA1_Final  (b->data, &h->u.sha1);     break;
@@ -137,7 +138,7 @@ gnutls_hash(h->sha, data, len);
 void
 exim_sha_finish(hctx * h, blob * b)
 {
-b->data = store_get(b->len = h->hashlen);
+b->data = store_get(b->len = h->hashlen, FALSE);
 gnutls_hash_output(h->sha, b->data);
 }


@@ -174,7 +175,7 @@ gcry_md_write(h->sha, data, len);
void
exim_sha_finish(hctx * h, blob * b)
{
-b->data = store_get(b->len = h->hashlen);
+b->data = store_get(b->len = h->hashlen, FALSE);
memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen);
}

@@ -212,7 +213,7 @@ switch (h->method)
 void
 exim_sha_finish(hctx * h, blob * b)
 {
-b->data = store_get(b->len = h->hashlen);
+b->data = store_get(b->len = h->hashlen, FALSE);
 switch (h->method)
   {
   case HASH_SHA1:   sha1_finish(h->u.sha1, b->data); break;
@@ -450,7 +451,7 @@ native_sha1_mid(&h->sha1, US data);    /* implicit size always 64 */
 void
 exim_sha_finish(hctx * h, blob * b)
 {
-b->data = store_get(b->len = h->hashlen);
+b->data = store_get(b->len = h->hashlen, FALSE);


native_sha1_end(&h->sha1, NULL, 0, b->data);
}
diff --git a/src/src/header.c b/src/src/header.c
index 76ea10f..a6c44fa 100644
--- a/src/src/header.c
+++ b/src/src/header.c
@@ -97,14 +97,17 @@ header_line *h, *new = NULL;
header_line **hptr;

uschar *p, *q;
-uschar buffer[HEADER_ADD_BUFFER_SIZE];
-gstring gs = { .size = HEADER_ADD_BUFFER_SIZE, .ptr = 0, .s = buffer };
+uschar * buf = store_get(HEADER_ADD_BUFFER_SIZE, FALSE);
+gstring gs = { .size = HEADER_ADD_BUFFER_SIZE, .ptr = 0, .s = buf };

if (!header_last) return NULL;

-if (!string_vformat(&gs, FALSE, format, ap))
+if (!string_vformat(&gs, SVFMT_REBUFFER, format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: "
     "%.100s ...", string_from_gstring(&gs));
+
+if (gs.s != buf) store_release_above(buf);
+gstring_release_unused(&gs);
 string_from_gstring(&gs);


/* Find where to insert this header */
@@ -166,7 +169,7 @@ else
point, we have hptr pointing to the link field that will point to the new
header, and h containing the following header, or NULL. */

-for (p = q = buffer; *p; p = q)
+for (p = q = gs.s; *p; p = q)
   {
   for (;;)
     {
@@ -175,7 +178,7 @@ for (p = q = buffer; *p; p = q)
     if (*(++q) != ' ' && *q != '\t') break;
     }


-  new = store_get(sizeof(header_line));
+  new = store_get(sizeof(header_line), FALSE);
   new->text = string_copyn(p, q - p);
   new->slen = q - p;
   new->type = type;
diff --git a/src/src/host.c b/src/src/host.c
index 9d94a2f..a00d048 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -197,9 +197,9 @@ if ((ipa = string_is_ip_address(lname, NULL)) != 0)
       (ipa == 6 && af == AF_INET6))
     {
     int x[4];
-    yield = store_get(sizeof(struct hostent));
-    alist = store_get(2 * sizeof(char *));
-    adds  = store_get(alen);
+    yield = store_get(sizeof(struct hostent), FALSE);
+    alist = store_get(2 * sizeof(char *), FALSE);
+    adds  = store_get(alen, FALSE);
     yield->h_name = CS name;
     yield->h_aliases = NULL;
     yield->h_addrtype = af;
@@ -251,9 +251,9 @@ else
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == type)
     count++;


- yield = store_get(sizeof(struct hostent));
- alist = store_get((count + 1) * sizeof(char *));
- adds = store_get(count *alen);
+ yield = store_get(sizeof(struct hostent), FALSE);
+ alist = store_get((count + 1) * sizeof(char *), FALSE);
+ adds = store_get(count *alen, FALSE);

   yield->h_name = CS name;
   yield->h_aliases = NULL;
@@ -325,7 +325,7 @@ while ((name = string_nextinlist(&list, &sep, NULL, 0)))
     continue;
     }


- h = store_get(sizeof(host_item));
+ h = store_get(sizeof(host_item), FALSE);
h->name = name;
h->address = NULL;
h->port = PORT_NONE;
@@ -524,12 +524,13 @@ void
host_build_sender_fullhost(void)
{
BOOL show_helo = TRUE;
-uschar * address, * fullhost, * rcvhost, * reset_point;
+uschar * address, * fullhost, * rcvhost;
+rmark reset_point;
int len;

if (!sender_host_address) return;

-reset_point = store_get(0);
+reset_point = store_mark();

 /* Set up address, with or without the port. After discussion, it seems that
 the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
@@ -643,10 +644,8 @@ else
     }
   }


-if (sender_fullhost) store_free(sender_fullhost);
-sender_fullhost = string_copy_malloc(fullhost);
-if (sender_rcvhost) store_free(sender_rcvhost);
-sender_rcvhost = string_copy_malloc(rcvhost);
+sender_fullhost = string_copy_perm(fullhost, TRUE);
+sender_rcvhost = string_copy_perm(rcvhost, TRUE);

store_reset(reset_point);

@@ -668,6 +667,8 @@ return depends on whether sender_fullhost and sender_ident are set or not:
ident set, no host => U=ident
ident set, host set => H=sender_fullhost U=ident

+Use taint-unchecked routines on the assumption we'll never expand the results.
+
 Arguments:
   useflag   TRUE if first item to be flagged (H= or U=); if there are two
               items, the second is always flagged
@@ -679,7 +680,7 @@ uschar *
 host_and_ident(BOOL useflag)
 {
 if (!sender_fullhost)
-  (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "",
+  string_format_nt(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "",
      sender_ident ? sender_ident : US"unknown");
 else
   {
@@ -688,10 +689,10 @@ else
   if (LOGGING(incoming_interface) && interface_address)
     iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port);
   if (sender_ident)
-    (void)string_format(big_buffer, big_buffer_size, "%s%s%s U=%s",
+    string_format_nt(big_buffer, big_buffer_size, "%s%s%s U=%s",
       flag, sender_fullhost, iface, sender_ident);
   else
-    (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
+    string_format_nt(big_buffer, big_buffer_size, "%s%s%s",
       flag, sender_fullhost, iface);
   }
 return big_buffer;
@@ -746,7 +747,7 @@ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
   address above. The field in the ip_address_item is large enough to hold an
   IPv6 address. */


- next = store_get(sizeof(ip_address_item));
+ next = store_get(sizeof(ip_address_item), FALSE);
next->next = NULL;
Ustrcpy(next->address, s);
next->port = port;
@@ -800,7 +801,7 @@ add_unique_interface(ip_address_item *list, ip_address_item *ipa)
ip_address_item *ipa2;
for (ipa2 = list; ipa2; ipa2 = ipa2->next)
if (Ustrcmp(ipa2->address, ipa->address) == 0) return list;
-ipa2 = store_get_perm(sizeof(ip_address_item));
+ipa2 = store_get_perm(sizeof(ip_address_item), FALSE);
*ipa2 = *ipa;
ipa2->next = list;
return ipa2;
@@ -816,7 +817,7 @@ ip_address_item *running_interfaces = NULL;

 if (local_interface_data == NULL)
   {
-  void *reset_item = store_get(0);
+  void *reset_item = store_mark();
   ip_address_item *dlist = host_build_ifacelist(CUS local_interfaces,
     US"local_interfaces");
   ip_address_item *xlist = host_build_ifacelist(CUS extra_local_interfaces,
@@ -1549,7 +1550,7 @@ if (  slow_lookup_log


/* Failed to look up the host. */

-if (hosts == NULL)
+if (!hosts)
   {
   HDEBUG(D_host_lookup) debug_printf("IP address lookup failed: h_errno=%d\n",
     h_errno);
@@ -1560,7 +1561,7 @@ if (hosts == NULL)
 treat this as non-existent. In some operating systems, this is returned as an
 empty string; in others as a single dot. */


-if (hosts->h_name == NULL || hosts->h_name[0] == 0 || hosts->h_name[0] == '.')
+if (!hosts->h_name || !hosts->h_name[0] || hosts->h_name[0] == '.')
   {
   HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an empty name: "
     "treated as non-existent host name\n");
@@ -1570,29 +1571,29 @@ if (hosts->h_name == NULL || hosts->h_name[0] == 0 || hosts->h_name[0] == '.')
 /* Copy and lowercase the name, which is in static storage in many systems.
 Put it in permanent memory. */


-s = US hosts->h_name;
-len = Ustrlen(s) + 1;
-t = sender_host_name = store_get_perm(len);
-while (*s != 0) *t++ = tolower(*s++);
-*t = 0;
+  {
+  int old_pool = store_pool;
+  store_pool = POOL_TAINT_PERM;        /* names are tainted */
+
+  sender_host_name = string_copylc(US hosts->h_name);


-/* If the host has aliases, build a copy of the alias list */
+ /* If the host has aliases, build a copy of the alias list */

-if (hosts->h_aliases)
-  {
-  int count = 1;
-  uschar **ptr;
-  for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++;
-  ptr = sender_host_aliases = store_get_perm(count * sizeof(uschar *));
-  for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++)
-    {
-    uschar *s = *aliases;
-    int len = Ustrlen(s) + 1;
-    uschar *t = *ptr++ = store_get_perm(len);
-    while (*s != 0) *t++ = tolower(*s++);
-    *t = 0;
-    }
-  *ptr = NULL;
+  if (hosts->h_aliases)
+    {
+    int count = 1;
+    uschar **ptr;
+
+    for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++;
+    store_pool = POOL_PERM;
+    ptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE);
+    store_pool = POOL_TAINT_PERM;
+
+    for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++)
+      *ptr++ = string_copylc(*aliases);
+    *ptr = NULL;
+    }
+  store_pool = old_pool;
   }


 return OK;
@@ -1707,7 +1708,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
       /* Get store for the list of aliases. For compatibility with
       gethostbyaddr, we make an empty list if there are none. */


-      aptr = sender_host_aliases = store_get(count * sizeof(uschar *));
+      aptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE);


       /* Re-scan and extract the names */


@@ -1715,7 +1716,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
            rr;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
         {
-        uschar * s = store_get(ssize);
+        uschar * s = store_get(ssize, TRUE);    /* names are tainted */


         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
@@ -1728,8 +1729,8 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
           break;
           }


-        store_reset(s + Ustrlen(s) + 1);
-        if (s[0] == 0)
+        store_release_above(s + Ustrlen(s) + 1);
+        if (!s[0])
           {
           HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
             "empty name: treated as non-existent host name\n");
@@ -1737,15 +1738,15 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
           }
         if (!sender_host_name) sender_host_name = s;
     else *aptr++ = s;
-        while (*s != 0) { *s = tolower(*s); s++; }
+        while (*s) { *s = tolower(*s); s++; }
         }


       *aptr = NULL;            /* End of alias list */
       store_pool = old_pool;   /* Reset store pool */


-      /* If we've found a names, break out of the "order" loop */
+      /* If we've found a name, break out of the "order" loop */


-      if (sender_host_name != NULL) break;
+      if (sender_host_name) break;
       }


     /* If the DNS lookup deferred, we must also defer. */
@@ -2113,7 +2114,7 @@ for (int i = 1; i <= times;


     else
       {
-      host_item *next = store_get(sizeof(host_item));
+      host_item *next = store_get(sizeof(host_item), FALSE);
       next->name = host->name;
       next->mx = host->mx;
       next->address = text_address;
@@ -2435,7 +2436,7 @@ for (; i >= 0; i--)
     /* Not a duplicate */


     new_sort_key = host->mx * 1000 + random_number(500) + randoffset;
-    next = store_get(sizeof(host_item));
+    next = store_get(sizeof(host_item), FALSE);


     /* New address goes first: insert the new block after the first one
     (so as not to disturb the original pointer) but put the new address
@@ -2838,7 +2839,7 @@ for (dns_record * rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   /* Make a new host item and seek the correct insertion place */
     {
     int sort_key = precedence * 1000 + weight;
-    host_item *next = store_get(sizeof(host_item));
+    host_item *next = store_get(sizeof(host_item), FALSE);
     next->name = string_copy_dnsdomain(data);
     next->address = NULL;
     next->port = port;
diff --git a/src/src/ip.c b/src/src/ip.c
index 0f25df1..19be51a 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -515,7 +515,7 @@ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)


 callout_address = string_copy(path);
 server.sun_family = AF_UNIX;
-Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1);
+Ustrncpy(US server.sun_path, path, sizeof(server.sun_path)-1);
 server.sun_path[sizeof(server.sun_path)-1] = '\0';
 if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
   {
diff --git a/src/src/local_scan.h b/src/src/local_scan.h
index dced8bd..b250a16 100644
--- a/src/src/local_scan.h
+++ b/src/src/local_scan.h
@@ -188,7 +188,10 @@ extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
 extern int     smtp_fflush(void);
 extern void    smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3);
 extern void    smtp_vprintf(const char *, BOOL, va_list);
-extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
+
+#define string_sprintf(fmt, ...) \
+    string_sprintf_trc(fmt, US __FUNCTION__, __LINE__, __VA_ARGS__)
+extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned, ...) ALMOST_PRINTF(1,4);


#ifdef LOCAL_SCAN
/* When compiling a local_scan() file we want to rename a published API, so that
diff --git a/src/src/log.c b/src/src/log.c
index 0aaf94a..e2543a7 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -865,9 +865,13 @@ DEBUG(D_any|D_v)

if (flags & LOG_CONFIG) g = log_config_info(g, flags);

+  /* We want to be able to log tainted info, but log_buffer is directly
+  malloc'd.  So use deliberately taint-nonchecking routines to build into
+  it, trusting that we will never expand the results. */
+
   va_start(ap, format);
   i = g->ptr;
-  if (!string_vformat(g, FALSE, format, ap))
+  if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
     {
     g->ptr = i;
     g = string_cat(g, US"**** log string overflowed log buffer ****");
@@ -921,7 +925,12 @@ if (flags & LOG_CONFIG)
 va_start(ap, format);
   {
   int i = g->ptr;
-  if (!string_vformat(g, FALSE, format, ap))
+
+  /* We want to be able to log tainted info, but log_buffer is directly
+  malloc'd.  So use deliberately taint-nonchecking routines to build into
+  it, trusting that we will never expand the results. */
+
+  if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
     {
     g->ptr = i;
     g = string_cat(g, US"**** log string overflowed log buffer ****\n");
@@ -934,7 +943,7 @@ this way because it kind of fits with LOG_RECIPIENTS. */


 if (   flags & LOG_SENDER
    && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
-  g = string_fmt_append(g, " from <%s>", raw_sender);
+  g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " from <%s>", raw_sender);


 /* Add list of recipients to the message if required; the raw list,
 before rewriting, was saved in raw_recipients. There may be none, if an ACL
@@ -945,12 +954,12 @@ if (  flags & LOG_RECIPIENTS
    && raw_recipients_count > 0)
   {
   int i;
-  g = string_fmt_append(g, " for");
+  g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " for", NULL);
   for (i = 0; i < raw_recipients_count; i++)
     {
     uschar * s = raw_recipients[i];
     if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
-    g = string_fmt_append(g, " %s", s);
+    g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", s);
     }
   }


@@ -1046,39 +1055,34 @@ if (flags & LOG_REJECT)
   {
   if (header_list && LOGGING(rejected_header))
     {
-    uschar * p = g->s + g->ptr;
+    gstring * g2;
     int i;


     if (recipients_count > 0)
       {
       /* List the sender */


-      string_format(p, LOG_BUFFER_SIZE - g->ptr,
-        "Envelope-from: <%s>\n", sender_address);
-      while (*p) p++;
-      g->ptr = p - g->s;
+      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+            "Envelope-from: <%s>\n", sender_address);
+      if (g2) g = g2;


       /* List up to 5 recipients */


-      string_format(p, LOG_BUFFER_SIZE - g->ptr,
-        "Envelope-to: <%s>\n", recipients_list[0].address);
-      while (*p) p++;
-      g->ptr = p - g->s;
+      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+            "Envelope-to: <%s>\n", recipients_list[0].address);
+      if (g2) g = g2;


       for (i = 1; i < recipients_count && i < 5; i++)
         {
-        string_format(p, LOG_BUFFER_SIZE - g->ptr, "    <%s>\n",
-          recipients_list[i].address);
-    while (*p) p++;
-    g->ptr = p - g->s;
+        g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+            "    <%s>\n", recipients_list[i].address);
+    if (g2) g = g2;
         }


       if (i < recipients_count)
         {
-        string_format(p, LOG_BUFFER_SIZE - g->ptr,
-          "    ...\n");
-    while (*p) p++;
-    g->ptr = p - g->s;
+        g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "    ...\n", NULL);
+    if (g2) g = g2;
         }
       }


@@ -1086,11 +1090,11 @@ if (flags & LOG_REJECT)

     for (header_line * h = header_list; h; h = h->next) if (h->text)
       {
-      BOOL fitted = string_format(p, LOG_BUFFER_SIZE - g->ptr,
-        "%c %s", h->type, h->text);
-      while (*p) p++;
-      g->ptr = p - g->s;
-      if (!fitted)         /* Buffer is full; truncate */
+      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+            "%c %s", h->type, h->text);
+      if (g2)
+    g = g2;
+      else        /* Buffer is full; truncate */
         {
         g->ptr -= 100;        /* For message and separator */
         if (g->s[g->ptr-1] == '\n') g->ptr--;
diff --git a/src/src/lookups/cdb.c b/src/src/lookups/cdb.c
index 13fe8b7..5cae153 100644
--- a/src/src/lookups/cdb.c
+++ b/src/src/lookups/cdb.c
@@ -184,7 +184,7 @@ cdb_open(uschar *filename,
   }


/* Having got a file open we need the structure to put things in */
- cdbp = store_get(sizeof(struct cdb_state));
+ cdbp = store_get(sizeof(struct cdb_state), FALSE);
/* store_get() does not return if memory was not available... */
/* preload the structure.... */
cdbp->fileno = fileno;
@@ -222,7 +222,7 @@ cdb_open(uschar *filename,

   /* get a buffer to stash the basic offsets in - this should speed
    * things up a lot - especially on multiple lookups */
-  cdbp->cdb_offsets = store_get(CDB_HASH_TABLE);
+  cdbp->cdb_offsets = store_get(CDB_HASH_TABLE, FALSE);


/* now fill the buffer up... */
if (cdb_bread(fileno, cdbp->cdb_offsets, CDB_HASH_TABLE) == -1) {
@@ -365,9 +365,10 @@ if (cdbp->cdb_map != NULL)

        item_ptr += item_key_len;


-       /* ... and the returned result */
+       /* ... and the returned result.  Assume it is not
+       tainted, lacking any way of telling.  */


-       *result = store_get(item_dat_len + 1);
+       *result = store_get(item_dat_len + 1, FALSE);
        memcpy(*result, item_ptr, item_dat_len);
        (*result)[item_dat_len] = 0;
        return OK;
@@ -410,31 +411,32 @@ for (int loop = 0; (loop < hash_offlen); ++loop)


     if (item_key_len == key_len)
       {                    /* finally check if key matches */
-      uschar * item_key = store_get(key_len);
+      rmark reset_point = store_mark();
+      uschar * item_key = store_get(key_len, TRUE); /* keys liable to be tainted */


       if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
-      if (Ustrncmp(keystring, item_key, key_len) == 0) {
-
-       /* Reclaim some store */
-       store_reset(item_key);
-
-       /* matches - get data length */
-       item_dat_len = cdb_unpack(packbuf + 4);
-
-       /* then we build a new result string.  We know we have enough
-       memory so disable Coverity errors about the tainted item_dat_ken */
-
-       *result = store_get(item_dat_len + 1);
-       /* coverity[tainted_data] */
-       if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
-     return DEFER;
-
-       /* coverity[tainted_data] */
-       (*result)[item_dat_len] = 0;
-       return OK;
-      }
+      if (Ustrncmp(keystring, item_key, key_len) == 0)
+        {
+        /* Reclaim some store */
+        store_reset(reset_point);
+
+        /* matches - get data length */
+        item_dat_len = cdb_unpack(packbuf + 4);
+
+        /* then we build a new result string.  We know we have enough
+        memory so disable Coverity errors about the tainted item_dat_ken */
+
+        *result = store_get(item_dat_len + 1, FALSE);
+        /* coverity[tainted_data] */
+        if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
+      return DEFER;
+
+        /* coverity[tainted_data] */
+        (*result)[item_dat_len] = 0;
+        return OK;
+        }
       /* Reclaim some store */
-      store_reset(item_key);
+      store_reset(reset_point);
       }
     }
   cur_offset += 8;
diff --git a/src/src/lookups/dbmdb.c b/src/src/lookups/dbmdb.c
index bdc002d..5e97009 100644
--- a/src/src/lookups/dbmdb.c
+++ b/src/src/lookups/dbmdb.c
@@ -154,7 +154,7 @@ int buflen, bufleft, key_item_len, sep = 0;
 or less than, the length of the delimited list passed in + 1. */


buflen = length + 3;
-key_buffer = store_get(buflen);
+key_buffer = store_get(buflen, is_tainted(keystring));

key_buffer[0] = '\0';

diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index 70203fa..5654b56 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -549,7 +549,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))

/* Reclaim unused memory */

-store_reset(yield->s + yield->ptr + 1);
+gstring_release_unused(yield);

/* If yield NULL we have not found anything. Otherwise, insert the terminating
zero and return the result. */
diff --git a/src/src/lookups/ibase.c b/src/src/lookups/ibase.c
index eab3c6a..f08f503 100644
--- a/src/src/lookups/ibase.c
+++ b/src/src/lookups/ibase.c
@@ -113,6 +113,7 @@ isc_stmt_handle stmth = NULL;
XSQLDA *out_sqlda;
XSQLVAR *var;
int i;
+rmark reset_point;

char buffer[256];
ISC_STATUS status[20], *statusp = status;
@@ -175,7 +176,7 @@ if (cn)
}
else
{
- cn = store_get(sizeof(ibase_connection));
+ cn = store_get(sizeof(ibase_connection), FALSE);
cn->server = server_copy;
cn->dbh = NULL;
cn->transh = NULL;
@@ -248,7 +249,9 @@ if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth))
goto IBASE_EXIT;
}

-out_sqlda = store_get(XSQLDA_LENGTH(1));
+/* Lacking any information, assume that the data is untainted */
+reset_point = store_mark();
+out_sqlda = store_get(XSQLDA_LENGTH(1), FALSE);
out_sqlda->version = SQLDA_VERSION1;
out_sqlda->sqln = 1;

@@ -256,7 +259,7 @@ if (isc_dsql_prepare
     (status, &cn->transh, &stmth, 0, query, 1, out_sqlda))
   {
   isc_interprete(buffer, &statusp);
-  store_reset(out_sqlda);
+  reset_point = store_reset(reset_point);
   out_sqlda = NULL;
   *errmsg =
       string_sprintf("Interbase prepare_statement() failed: %s",
@@ -268,13 +271,13 @@ if (isc_dsql_prepare
 /* re-allocate the output structure if there's more than one field */
 if (out_sqlda->sqln < out_sqlda->sqld)
   {
-  XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
+  XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), FALSE);
   if (isc_dsql_describe
       (status, &stmth, out_sqlda->version, new_sqlda))
     {
     isc_interprete(buffer, &statusp);
     isc_dsql_free_statement(status, &stmth, DSQL_drop);
-    store_reset(out_sqlda);
+    reset_point = store_reset(reset_point);
     out_sqlda = NULL;
     *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
                buffer);
@@ -290,46 +293,46 @@ for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
   switch (var->sqltype & ~1)
     {
     case SQL_VARYING:
-    var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2);
+    var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, FALSE);
     break;
     case SQL_TEXT:
-    var->sqldata = CS store_get(sizeof(char) * var->sqllen);
+    var->sqldata = CS store_get(sizeof(char) * var->sqllen, FALSE);
     break;
     case SQL_SHORT:
-    var->sqldata = CS  store_get(sizeof(short));
+    var->sqldata = CS  store_get(sizeof(short), FALSE);
     break;
     case SQL_LONG:
-    var->sqldata = CS  store_get(sizeof(ISC_LONG));
+    var->sqldata = CS  store_get(sizeof(ISC_LONG), FALSE);
     break;
 #ifdef SQL_INT64
     case SQL_INT64:
-    var->sqldata = CS  store_get(sizeof(ISC_INT64));
+    var->sqldata = CS  store_get(sizeof(ISC_INT64), FALSE);
     break;
 #endif
     case SQL_FLOAT:
-    var->sqldata = CS  store_get(sizeof(float));
+    var->sqldata = CS  store_get(sizeof(float), FALSE);
     break;
     case SQL_DOUBLE:
-    var->sqldata = CS  store_get(sizeof(double));
+    var->sqldata = CS  store_get(sizeof(double), FALSE);
     break;
 #ifdef SQL_TIMESTAMP
     case SQL_DATE:
-    var->sqldata = CS  store_get(sizeof(ISC_QUAD));
+    var->sqldata = CS  store_get(sizeof(ISC_QUAD), FALSE);
     break;
 #else
     case SQL_TIMESTAMP:
-    var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP));
+    var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP), FALSE);
     break;
     case SQL_TYPE_DATE:
-    var->sqldata = CS  store_get(sizeof(ISC_DATE));
+    var->sqldata = CS  store_get(sizeof(ISC_DATE), FALSE);
     break;
     case SQL_TYPE_TIME:
-    var->sqldata = CS  store_get(sizeof(ISC_TIME));
+    var->sqldata = CS  store_get(sizeof(ISC_TIME), FALSE);
     break;
   #endif
     }
   if (var->sqltype & 1)
-    var->sqlind = (short *) store_get(sizeof(short));
+    var->sqlind = (short *) store_get(sizeof(short), FALSE);
   }


/* finally, we're ready to execute the statement */
@@ -411,7 +414,7 @@ if (!result)
*errmsg = US "Interbase: no data found";
}
else
- store_reset(result->s + result->ptr + 1);
+ gstring_release_unused(result);


/* Get here by goto from various error checks. */
@@ -514,7 +517,7 @@ static uschar *ibase_quote(uschar * s, uschar * opt)

     if (count == 0)
         return s;
-    t = quoted = store_get(Ustrlen(s) + count + 1);
+    t = quoted = store_get(Ustrlen(s) + count + 1, FALSE);


     while ((c = *s++) != 0) {
         if (Ustrchr("'", c) != NULL) {
diff --git a/src/src/lookups/json.c b/src/src/lookups/json.c
index a02b7b9..15b8617 100644
--- a/src/src/lookups/json.c
+++ b/src/src/lookups/json.c
@@ -16,12 +16,14 @@ which is freed once by search_tidyup(). Make the free call a dummy.
 This burns some 300kB in handling a 37kB JSON file, for the benefit of
 a fast free.  The alternative of staying with malloc is nearly as bad,
 eyeballing the activity there are 20% the number of free vs. alloc
-calls (before the big bunch at the end). */
+calls (before the big bunch at the end).
+
+Assume that the file is trusted, so no tainting */


static void *
json_malloc(size_t nbytes)
{
-void * p = store_get((int)nbytes);
+void * p = store_get((int)nbytes, FALSE);
/* debug_printf("%s %d: %p\n", __FUNCTION__, (int)nbytes, p); */
return p;
}
diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c
index 97ee188..5b0cffa 100644
--- a/src/src/lookups/ldap.c
+++ b/src/src/lookups/ldap.c
@@ -301,24 +301,18 @@ if (!lcp)
than the host name + "ldaps:///" plus : and a port number, say 20 + the
length of the host name. What we get should accommodate both, easily. */

- uschar *shost = (host == NULL)? US"" : host;
- uschar *init_url = store_get(20 + 3 * Ustrlen(shost));
- uschar *init_ptr;
+ uschar * shost = host ? host : US"";
+ rmark reset_point = store_mark();
+ gstring * g;

/* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to
contain the path name, with slashes escaped as %2F. */

   if (ldapi)
     {
-    int ch;
-    init_ptr = init_url + 8;
-    Ustrcpy(init_url, "ldapi://");
-    while ((ch = *shost++))
-      if (ch == '/')
-    { Ustrncpy(init_ptr, "%2F", 3); init_ptr += 3; }
-      else
-    *init_ptr++ = ch;
-    *init_ptr = 0;
+    g = string_catn(NULL, US"ldapi://", 8);
+    for (uschar ch; (ch = *shost); shost++)
+      g = ch == '/' ? string_catn(g, US"%2F", 3) : string_catn(g, shost, 1);
     }


/* This is not an ldapi call. Just build a URI with the protocol type, host
@@ -326,22 +320,22 @@ if (!lcp)

   else
     {
-    init_ptr = Ustrchr(ldap_url, '/');
-    Ustrncpy(init_url, ldap_url, init_ptr - ldap_url);
-    init_ptr = init_url + (init_ptr - ldap_url);
-    sprintf(CS init_ptr, "//%s:%d/", shost, port);
+    uschar * init_ptr = Ustrchr(ldap_url, '/');
+    g = string_catn(NULL, ldap_url, init_ptr - ldap_url);
+    g = string_fmt_append(g, "//%s:%d/", shost, port);
     }
+  string_from_gstring(g);


/* Call ldap_initialize() and check the result */

-  DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", init_url);
-  if ((rc = ldap_initialize(&ld, CS init_url)) != LDAP_SUCCESS)
+  DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", g->s);
+  if ((rc = ldap_initialize(&ld, CS g->s)) != LDAP_SUCCESS)
     {
     *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
-      rc, init_url);
+      rc, g->s);
     goto RETURN_ERROR;
     }
-  store_reset(init_url);   /* Might as well save memory when we can */
+  store_reset(reset_point);   /* Might as well save memory when we can */



/* ------------------------- Not OpenLDAP ---------------------- */
@@ -501,8 +495,8 @@ if (!lcp)

/* Now add this connection to the chain of cached connections */

- lcp = store_get(sizeof(LDAP_CONNECTION));
- lcp->host = (host == NULL)? NULL : string_copy(host);
+ lcp = store_get(sizeof(LDAP_CONNECTION), FALSE);
+ lcp->host = host ? string_copy(host) : NULL;
lcp->bound = FALSE;
lcp->user = NULL;
lcp->password = NULL;
@@ -1004,7 +998,7 @@ if (search_type != SEARCH_LDAP_MULTIPLE && rescount > 1)

 if (rescount < 1)
   {
-  *errmsg = string_sprintf("LDAP search: no results");
+  *errmsg = US"LDAP search: no results";
   error_yield = FAIL;
   goto RETURN_ERROR_BREAK;
   }
@@ -1156,6 +1150,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
         else
           {
+          *errmsg = US"LDAP option REFERRALS is not \"follow\" or \"nofollow\"";
           DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
           return DEFER;
           }
@@ -1481,7 +1476,7 @@ if (count == 0) return s;


/* Get sufficient store to hold the quoted string */

-t = quoted = store_get(len + count + 1);
+t = quoted = store_get(len + count + 1, is_tainted(s));

/* Handle plain quote_ldap */

@@ -1536,7 +1531,7 @@ else
       {
       if (Ustrchr(LDAP_DN_QUOTE, c) != NULL)
         {
-        Ustrncpy(t, "%5C", 3);               /* insert \ where needed */
+        Ustrncpy(t, US"%5C", 3);               /* insert \ where needed */
         t += 3;                              /* fall through to check URL */
         }
       if (Ustrchr(URL_NONQUOTE, c) == NULL)  /* e.g. ] => %5D */
@@ -1553,7 +1548,7 @@ else


   while (*ss++ != 0)
     {
-    Ustrncpy(t, "%5C%20", 6);
+    Ustrncpy(t, US"%5C%20", 6);
     t += 6;
     }
   }
diff --git a/src/src/lookups/lmdb.c b/src/src/lookups/lmdb.c
index 931ea5b..2976cfa 100644
--- a/src/src/lookups/lmdb.c
+++ b/src/src/lookups/lmdb.c
@@ -30,7 +30,7 @@ Lmdbstrct * lmdb_p;
 int ret, save_errno;
 const uschar * errstr;


-lmdb_p = store_get(sizeof(Lmdbstrct));
+lmdb_p = store_get(sizeof(Lmdbstrct), FALSE);
lmdb_p->txn = NULL;

if ((ret = mdb_env_create(&db_env)))
diff --git a/src/src/lookups/lsearch.c b/src/src/lookups/lsearch.c
index 8b4459a..76b76b8 100644
--- a/src/src/lookups/lsearch.c
+++ b/src/src/lookups/lsearch.c
@@ -78,7 +78,7 @@ FILE *f = (FILE *)handle;
BOOL last_was_eol = TRUE;
BOOL this_is_eol = TRUE;
int old_pool = store_pool;
-void *reset_point = NULL;
+rmark reset_point = NULL;
uschar buffer[4096];

/* Wildcard searches may use up some store, because of expansions. We don't
@@ -90,7 +90,7 @@ safely stored in the search pool again. */
if(type == LSEARCH_WILD || type == LSEARCH_NWILD)
{
store_pool = POOL_MAIN;
- reset_point = store_get(0);
+ reset_point = store_mark();
}

filename = filename; /* Keep picky compilers happy */
@@ -241,7 +241,7 @@ for (last_was_eol = TRUE;

   if (reset_point)
     {
-    store_reset(reset_point);
+    reset_point = store_reset(reset_point);
     store_pool = old_pool;
     }


@@ -294,7 +294,7 @@ for (last_was_eol = TRUE;
     yield = string_cat(yield, s);
     }


- store_reset(yield->s + yield->ptr + 1);
+ gstring_release_unused(yield);
*result = string_from_gstring(yield);
return OK;
}
diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c
index 1984e30..460ee29 100644
--- a/src/src/lookups/mysql.c
+++ b/src/src/lookups/mysql.c
@@ -232,7 +232,7 @@ if (!cn)

/* Get store for a new handle, initialize it, and connect to the server */

- mysql_handle = store_get(sizeof(MYSQL));
+ mysql_handle = store_get(sizeof(MYSQL), FALSE);
mysql_init(mysql_handle);
mysql_options(mysql_handle, MYSQL_READ_DEFAULT_GROUP, CS group);
if (mysql_real_connect(mysql_handle,
@@ -248,7 +248,7 @@ if (!cn)

/* Add the connection to the cache */

- cn = store_get(sizeof(mysql_connection));
+ cn = store_get(sizeof(mysql_connection), FALSE);
cn->server = server_copy;
cn->handle = mysql_handle;
cn->next = mysql_connections;
@@ -366,7 +366,7 @@ if (mysql_result) mysql_free_result(mysql_result);
if (result)
{
*resultptr = string_from_gstring(result);
- store_reset(result->s + (result->size = result->ptr + 1));
+ gstring_release_unused(result);
return OK;
}
else
@@ -432,7 +432,7 @@ while ((c = *t++) != 0)
if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;

if (count == 0) return s;
-t = quoted = store_get(Ustrlen(s) + count + 1);
+t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s));

 while ((c = *s++) != 0)
   {
diff --git a/src/src/lookups/nis.c b/src/src/lookups/nis.c
index d3f0480..6f5307f 100644
--- a/src/src/lookups/nis.c
+++ b/src/src/lookups/nis.c
@@ -24,7 +24,7 @@ nis_open(uschar *filename, uschar **errmsg)
 char *nis_domain;
 if (yp_get_default_domain(&nis_domain) != 0)
   {
-  *errmsg = string_sprintf("failed to get default NIS domain");
+  *errmsg = US"failed to get default NIS domain";
   return NULL;
   }
 return nis_domain;
diff --git a/src/src/lookups/nisplus.c b/src/src/lookups/nisplus.c
index 6a3351e..98f3df3 100644
--- a/src/src/lookups/nisplus.c
+++ b/src/src/lookups/nisplus.c
@@ -195,7 +195,7 @@ if (field_name)
   *errmsg = string_sprintf("NIS+ field %s not found for %s", field_name,
     query);
 else
-  store_reset(yield->s + yield->ptr + 1);
+  gstring_release_unused(yield);


/* Free result store before finishing. */

@@ -240,7 +240,7 @@ if (opt != NULL) return NULL;    /* No options recognized */
 while (*t != 0) if (*t++ == '\"') count++;
 if (count == 0) return s;


-t = quoted = store_get(Ustrlen(s) + count + 1);
+t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s));

while (*s != 0)
{
diff --git a/src/src/lookups/oracle.c b/src/src/lookups/oracle.c
index d106c51..4e8cba5 100644
--- a/src/src/lookups/oracle.c
+++ b/src/src/lookups/oracle.c
@@ -305,8 +305,8 @@ if (!cn)

/* Get store for a new connection, initialize it, and connect to the server */

-   oracle_handle = store_get(sizeof(struct cda_def));
-   hda = store_get(HDA_SIZE);
+   oracle_handle = store_get(sizeof(struct cda_def), FALSE);
+   hda = store_get(HDA_SIZE, FALSE);
    memset(hda,'\0',HDA_SIZE);


/*
@@ -329,7 +329,7 @@ if (!cn)

/* Add the connection to the cache */

- cn = store_get(sizeof(oracle_connection));
+ cn = store_get(sizeof(oracle_connection), FALSE);
cn->server = server_copy;
cn->handle = oracle_handle;
cn->next = oracle_connections;
@@ -348,7 +348,7 @@ else

/* We have a connection. Open a cursor and run the query */

-cda = store_get(sizeof(Cda_Def));
+cda = store_get(sizeof(Cda_Def), FALSE);

if (oopen(cda, oracle_handle, (text *)0, -1, -1, (text *)0, -1) != 0)
{
@@ -369,8 +369,8 @@ if (oparse(cda, (text *)query, (sb4) -1,
/* Find the number of fields returned and sort out their types. If the number
is one, we don't add field names to the data. Otherwise we do. */

-def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE);
-desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE);
+def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE, FALSE);
+desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE, FALSE);

if ((num_fields = describe_define(cda,def,desc)) == -1)
{
@@ -465,7 +465,7 @@ if (!result)
*errmsg = "ORACLE: no data found";
}
else
- store_reset(result->s + result->ptr + 1);
+ gstring_release_unused(result);

/* Get here by goto from various error checks. */

@@ -561,7 +561,7 @@ while ((c = *t++) != 0)
if (strchr("\n\t\r\b\'\"\\", c) != NULL) count++;

if (count == 0) return s;
-t = quoted = store_get((int)strlen(s) + count + 1);
+t = quoted = store_get((int)strlen(s) + count + 1, is_tainted(s));

while ((c = *s++) != 0)
{
diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c
index cf1e174..b5f6093 100644
--- a/src/src/lookups/pgsql.c
+++ b/src/src/lookups/pgsql.c
@@ -128,6 +128,7 @@ gstring * result = NULL;
int yield = DEFER;
unsigned int num_fields, num_tuples;
pgsql_connection *cn;
+rmark reset_point = store_mark();
uschar *server_copy = NULL;
uschar *sdata[3];

@@ -238,7 +239,7 @@ if (!cn)

   if(PQstatus(pg_conn) == CONNECTION_BAD)
     {
-    store_reset(server_copy);
+    reset_point = store_reset(reset_point);
     *errmsg = string_sprintf("PGSQL connection failed: %s",
       PQerrorMessage(pg_conn));
     PQfinish(pg_conn);
@@ -259,7 +260,7 @@ if (!cn)


/* Add the connection to the cache */

- cn = store_get(sizeof(pgsql_connection));
+ cn = store_get(sizeof(pgsql_connection), FALSE);
cn->server = server_copy;
cn->handle = pg_conn;
cn->next = pgsql_connections;
@@ -356,7 +357,7 @@ if (pg_result) PQclear(pg_result);

if (result)
{
- store_reset(result->s + result->ptr + 1);
+ gstring_release_unused(result);
*resultptr = string_from_gstring(result);
return OK;
}
@@ -427,7 +428,7 @@ while ((c = *t++) != 0)
if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;

if (count == 0) return s;
-t = quoted = store_get(Ustrlen(s) + count + 1);
+t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s));

 while ((c = *s++) != 0)
   {
diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c
index 6de6757..1b53eed 100644
--- a/src/src/lookups/redis.c
+++ b/src/src/lookups/redis.c
@@ -163,13 +163,13 @@ if (!cn)
     socket ? redisConnectUnix(CCS socket) : redisConnect(CCS server, port);
   if (!redis_handle)
     {
-    *errmsg = string_sprintf("REDIS connection failed");
+    *errmsg = US"REDIS connection failed";
     *defer_break = FALSE;
     goto REDIS_EXIT;
     }


/* Add the connection to the cache */
- cn = store_get(sizeof(redis_connection));
+ cn = store_get(sizeof(redis_connection), FALSE);
cn->server = server_copy;
cn->handle = redis_handle;
cn->next = redis_connections;
@@ -333,7 +333,7 @@ switch (redis_reply->type)


if (result)
- store_reset(result->s + result->ptr + 1);
+ gstring_release_unused(result);
else
{
yield = FAIL;
@@ -416,7 +416,7 @@ while ((c = *t++) != 0)
if (isspace(c) || c == '\\') count++;

if (count == 0) return s;
-t = quoted = store_get(Ustrlen(s) + count + 1);
+t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s));

 while ((c = *s++) != 0)
   {
diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c
index 5da2de8..6200d6c 100644
--- a/src/src/lookups/sqlite.c
+++ b/src/src/lookups/sqlite.c
@@ -131,7 +131,7 @@ if (opt != NULL) return NULL;     /* No options recognized */
 while ((c = *t++) != 0) if (c == '\'') count++;


if (count == 0) return s;
-t = quoted = store_get(Ustrlen(s) + count + 1);
+t = quoted = store_get(Ustrlen(s) + count + 1, is_tainted(s));

 while ((c = *s++) != 0)
   {
diff --git a/src/src/macros.h b/src/src/macros.h
index 2c7900b..a94a71f 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -110,13 +110,6 @@ don't make the file descriptors two-way. */
 #define DEBUG(x)      if (debug_selector & (x))
 #define HDEBUG(x)     if (host_checking || (debug_selector & (x)))


-#define PTR_CHK(ptr) \
-do { \
-if ((void *)ptr > (void *)store_get(0)) \
-  debug_printf("BUG: ptr '%s' beyond arena at %s:%d\n", \
-           mac_expanded_string(ptr), __FUNCTION__, __LINE__); \
-} while(0)
-
 /* The default From: text for DSNs */


 #define DEFAULT_DSN_FROM "Mail Delivery System <Mailer-Daemon@$qualify_domain>"
@@ -1097,4 +1090,10 @@ should not be one active. */
     ": 0x18 :session resumed unasked: 0x1A :session resumed unasked" \
     ": 0x1C :session resumed: 0x1E :session resumed, also new ticket"


+/* Flags for string_vformat */
+#define SVFMT_EXTEND        BIT(0)
+#define SVFMT_REBUFFER        BIT(1)
+#define SVFMT_TAINT_NOCHK    BIT(2)
+
+
 /* End of macros.h */
diff --git a/src/src/malware.c b/src/src/malware.c
index 91649cf..481b46a 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -836,7 +836,7 @@ badseek:  err = errno;
         malware_daemon_ctx.sock);
       }


-    if (!(drweb_fbuf = US malloc(fsize_uint)))
+    if (!(drweb_fbuf = store_malloc(fsize_uint)))
       {
       (void)close(drweb_fd);
       return m_panic_defer_3(scanent, NULL,
@@ -849,7 +849,7 @@ badseek:  err = errno;
       {
       int err = errno;
       (void)close(drweb_fd);
-      free(drweb_fbuf);
+      store_free(drweb_fbuf);
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't read spool file %s: %s",
           eml_filename, strerror(err)),
@@ -860,11 +860,12 @@ badseek:  err = errno;
     /* send file body to socket */
     if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
       {
-      free(drweb_fbuf);
+      store_free(drweb_fbuf);
       return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
         "unable to send file body to socket (%s)", scanner_options),
         malware_daemon_ctx.sock);
       }
+    store_free(drweb_fbuf);
     }
       else
     {
@@ -917,7 +918,9 @@ badseek:  err = errno;
         return m_panic_defer_3(scanent, CUS callout_address,
                   US"cannot read report size", malware_daemon_ctx.sock);
       drweb_slen = ntohl(drweb_slen);
-      tmpbuf = store_get(drweb_slen);
+
+      /* assume tainted, since it is external input */
+      tmpbuf = store_get(drweb_slen, TRUE);


       /* read report body */
       if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
@@ -1463,7 +1466,7 @@ badseek:  err = errno;
     /* Local file; so we def want to use_scan_command and don't want to try
      * passing IP/port combinations */
     use_scan_command = TRUE;
-    cd = (clamd_address *) store_get(sizeof(clamd_address));
+    cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);


     /* extract socket-path part */
     sublist = scanner_options;
@@ -1497,7 +1500,7 @@ badseek:  err = errno;
         continue;
         }


-      cd = (clamd_address *) store_get(sizeof(clamd_address));
+      cd = (clamd_address *) store_get(sizeof(clamd_address), FALSE);


       /* extract host and port part */
       sublist = scanner_options;
@@ -1666,7 +1669,7 @@ b_seek:   err = errno;
     if (lseek(clam_fd, 0, SEEK_SET) < 0)
       goto b_seek;


-    if (!(clamav_fbuf = US malloc(fsize_uint)))
+    if (!(clamav_fbuf = store_malloc(fsize_uint)))
       {
       (void)close(clam_fd);
       return m_panic_defer_3(scanent, NULL,
@@ -1678,7 +1681,7 @@ b_seek:   err = errno;
     if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
       {
       int err = errno;
-      free(clamav_fbuf); (void)close(clam_fd);
+      store_free(clamav_fbuf); (void)close(clam_fd);
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("can't read spool file %s: %s",
           eml_filename, strerror(err)),
@@ -1693,13 +1696,12 @@ b_seek:   err = errno;
         (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
         (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
       {
-      free(clamav_fbuf);
+      store_free(clamav_fbuf);
       return m_panic_defer_3(scanent, NULL,
         string_sprintf("unable to send file body to socket (%s)", hostname),
         malware_daemon_ctx.sock);
       }
-
-    free(clamav_fbuf);
+    store_free(clamav_fbuf);
     }
       else
     { /* use scan command */
diff --git a/src/src/match.c b/src/src/match.c
index 43f5912..4cd9259 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -541,7 +541,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
     if (Ustrcmp(ss, "+caseful") == 0)
       {
       check_string_block *cb = (check_string_block *)arg;
-      Ustrcpy(cb->subject, cb->origsubject);
+      Ustrcpy(US cb->subject, cb->origsubject);
       cb->caseless = FALSE;
       continue;
       }
@@ -666,7 +666,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
             so we use the permanent store pool */


             store_pool = POOL_PERM;
-            p = store_get(sizeof(namedlist_cacheblock));
+            p = store_get(sizeof(namedlist_cacheblock), FALSE);
             p->key = string_copy(get_check_key(arg, type));



@@ -1277,6 +1277,7 @@ match_address_list(const uschar *address, BOOL caseless, BOOL expand,
{
check_address_block ab;
unsigned int *local_cache_bits = cache_bits;
+int len;

/* RFC 2505 recommends that for spam checking, local parts should be caselessly
compared. Therefore, Exim now forces the entire address into lower case here,
@@ -1285,8 +1286,10 @@ patterns.) Otherwise just the domain is lower cases. A magic item "+caseful" in
the list can be used to restore a caseful copy of the local part from the
original address. */

-sprintf(CS big_buffer, "%.*s", big_buffer_size - 1, address);
-for (uschar * p = big_buffer + Ustrlen(big_buffer) - 1; p >= big_buffer; p--)
+if ((len = Ustrlen(address)) > 255) len = 255;
+ab.address = string_copyn(address, len);
+
+for (uschar * p = ab.address + len - 1; p >= ab.address; p--)
{
if (!caseless && *p == '@') break;
*p = tolower(*p);
@@ -1307,7 +1310,7 @@ if (expand_setup == 0)
/* Set up the data to be passed ultimately to check_address. */

ab.origaddress = address;
-ab.address = big_buffer;
+/* ab.address is above */
ab.expand_setup = expand_setup;
ab.caseless = caseless;

diff --git a/src/src/mime.c b/src/src/mime.c
index cf537d7..d47b569 100644
--- a/src/src/mime.c
+++ b/src/src/mime.c
@@ -499,8 +499,8 @@ int rc = OK;
uschar * header = NULL;
struct mime_boundary_context nested_context;

-/* reserve a line buffer to work in */
-header = store_get(MIME_MAX_HEADER_SIZE+1);
+/* reserve a line buffer to work in. Assume tainted data. */
+header = store_get(MIME_MAX_HEADER_SIZE+1, TRUE);

 /* Not actually used at the moment, but will be vital to fixing
  * some RFC 2046 nonconformance later... */
diff --git a/src/src/moan.c b/src/src/moan.c
index cdec745..fea3683 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -308,7 +308,7 @@ if (bounce_return_message)
   if (bounce_return_body && message_file)
     {
     BOOL enddot = f.dot_ends && message_file == stdin;
-    uschar * buf = store_get(bounce_return_linesize_limit+2);
+    uschar * buf = store_get(bounce_return_linesize_limit+2, TRUE);


     if (firstline) fprintf(fp, "%s", CS firstline);


diff --git a/src/src/mytypes.h b/src/src/mytypes.h
index be11240..84baa9e 100644
--- a/src/src/mytypes.h
+++ b/src/src/mytypes.h
@@ -13,6 +13,8 @@ local_scan.h includes it and exim.h includes them both (to get this earlier). */
#ifndef MYTYPES_H
#define MYTYPES_H

+# include <string.h>
+
 #ifndef FALSE
 # define FALSE         0
 #endif
@@ -98,18 +100,20 @@ functions that are called quite often; for other calls to external libraries
 #define Uread(f,b,l)       read(f,CS(b),l)
 #define Urename(s,t)       rename(CCS(s),CCS(t))
 #define Ustat(s,t)         stat(CCS(s),t)
-#define Ustrcat(s,t)       strcat(CS(s),CCS(t))
+#define Ustrcat(s,t)       __Ustrcat(s,t, __FUNCTION__, __LINE__)
 #define Ustrchr(s,n)       US strchr(CCS(s),n)
 #define CUstrchr(s,n)      CUS strchr(CCS(s),n)
 #define CUstrerror(n)      CUS strerror(n)
 #define Ustrcmp(s,t)       strcmp(CCS(s),CCS(t))
-#define Ustrcpy(s,t)       strcpy(CS(s),CCS(t))
+#define Ustrcpy(s,t)       __Ustrcpy(s,t, __FUNCTION__, __LINE__)
+#define Ustrcpy_nt(s,t)    strcpy(CS s, CCS t)        /* no taint check */
 #define Ustrcspn(s,t)      strcspn(CCS(s),CCS(t))
 #define Ustrftime(s,m,f,t) strftime(CS(s),m,f,t)
 #define Ustrlen(s)         (int)strlen(CCS(s))
-#define Ustrncat(s,t,n)    strncat(CS(s),CCS(t),n)
+#define Ustrncat(s,t,n)    __Ustrncat(s,t,n, __FUNCTION__, __LINE__)
 #define Ustrncmp(s,t,n)    strncmp(CCS(s),CCS(t),n)
-#define Ustrncpy(s,t,n)    strncpy(CS(s),CCS(t),n)
+#define Ustrncpy(s,t,n)    __Ustrncpy(s,t,n, __FUNCTION__, __LINE__)
+#define Ustrncpy_nt(s,t,n) strncpy(CS s, CCS t, n)    /* no taint check */
 #define Ustrpbrk(s,t)      strpbrk(CCS(s),CCS(t))
 #define Ustrrchr(s,n)      US strrchr(CCS(s),n)
 #define CUstrrchr(s,n)     CUS strrchr(CCS(s),n)
@@ -121,6 +125,38 @@ functions that are called quite often; for other calls to external libraries
 #define Ustrtoul(s,t,b)    strtoul(CCS(s),CSS(t),b)
 #define Uunlink(s)         unlink(CCS(s))


+extern BOOL is_tainted(const void *);
+extern void die_tainted(const uschar *, const uschar *, int);
+
+static inline uschar * __Ustrcat(uschar * dst, const uschar * src, const char * func, int line)
+{
+#ifndef COMPILE_UTILITY
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcat", CUS func, line);
+#endif
+return US strcat(CS dst, CCS src);
+}
+static inline uschar * __Ustrcpy(uschar * dst, const uschar * src, const char * func, int line)
+{
+#ifndef COMPILE_UTILITY
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcpy", CUS func, line);
#endif
+return US strcpy(CS dst, CCS src);
+}
+static inline uschar * __Ustrncat(uschar * dst, const uschar * src, size_t n, const char * func, int line)
+{
+#ifndef COMPILE_UTILITY
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncat", CUS func, line);
+#endif
+return US strncat(CS dst, CCS src, n);
+}
+static inline uschar * __Ustrncpy(uschar * dst, const uschar * src, size_t n, const char * func, int line)
+{
+#ifndef COMPILE_UTILITY
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncpy", CUS func, line);
+#endif
+return US strncpy(CS dst, CCS src, n);
+}
+/*XXX will likely need unchecked copy also */

+#endif
/* End of mytypes.h */
diff --git a/src/src/os.c b/src/src/os.c
index 9c1281a..c44bd3e 100644
--- a/src/src/os.c
+++ b/src/src/os.c
@@ -508,7 +508,7 @@ for (struct ifaddrs * ifa = ifalist; ifa; ifa = ifa->ifa_next)
/* Create a data block for the address, fill in the data, and put it on the
chain. */

- next = store_get(sizeof(ip_address_item));
+ next = store_get(sizeof(ip_address_item), FALSE);
next->next = NULL;
next->port = 0;
(void)host_ntoa(-1, ifa->ifa_addr, next->address, NULL);
@@ -743,7 +743,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
/* Create a data block for the address, fill in the data, and put it on the
chain. */

- next = store_get(sizeof(ip_address_item));
+ next = store_get(sizeof(ip_address_item), FALSE);
next->next = NULL;
next->port = 0;
(void)host_ntoa(-1, addrp, next->address, NULL);
@@ -775,13 +775,13 @@ interfaces. We just return the loopback address(es). */
ip_address_item *
os_common_find_running_interfaces(void)
{
-ip_address_item *yield = store_get(sizeof(address_item));
+ip_address_item *yield = store_get(sizeof(address_item), FALSE);
yield->address = US"127.0.0.1";
yield->port = 0;
yield->next = NULL;

#if HAVE_IPV6
-yield->next = store_get(sizeof(address_item));
+yield->next = store_get(sizeof(address_item), FALSE);
yield->next->address = US"::1";
yield->next->port = 0;
yield->next->next = NULL;
diff --git a/src/src/parse.c b/src/src/parse.c
index 4b0efa0..e64cb94 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -23,7 +23,7 @@ redundant apparatus. */

address_item *deliver_make_addr(uschar *address, BOOL copy)
{
-address_item *addr = store_get(sizeof(address_item));
+address_item *addr = store_get(sizeof(address_item), FALSE);
addr->next = NULL;
addr->parent = NULL;
addr->address = address;
@@ -618,7 +618,7 @@ uschar *
parse_extract_address(uschar *mailbox, uschar **errorptr, int *start, int *end,
int *domain, BOOL allow_null)
{
-uschar *yield = store_get(Ustrlen(mailbox) + 1);
+uschar *yield = store_get(Ustrlen(mailbox) + 1, is_tainted(mailbox));
uschar *startptr, *endptr;
uschar *s = US mailbox;
uschar *t = US yield;
@@ -1396,7 +1396,7 @@ for (;;)

     if (flen <= 0)
       {
-      *error = string_sprintf("file name missing after :include:");
+      *error = US"file name missing after :include:";
       return FF_ERROR;
       }


@@ -1547,7 +1547,7 @@ for (;;)
       return FF_ERROR;
       }


-    filebuf = store_get(statbuf.st_size + 1);
+    filebuf = store_get(statbuf.st_size + 1, is_tainted(filename));
     if (fread(filebuf, 1, statbuf.st_size, f) != statbuf.st_size)
       {
       *error = string_sprintf("error while reading included file %s: %s",
@@ -1623,7 +1623,7 @@ for (;;)


     if ((*s == '|' || *s == '/') && (recipient == NULL || domain == 0))
       {
-      uschar *t = store_get(Ustrlen(s) + 1);
+      uschar *t = store_get(Ustrlen(s) + 1, is_tainted(s));
       uschar *p = t;
       uschar *q = s;
       while (*q != 0)
@@ -1662,7 +1662,7 @@ for (;;)


         if (syntax_errors != NULL)
           {
-          error_block *e = store_get(sizeof(error_block));
+          error_block *e = store_get(sizeof(error_block), FALSE);
           error_block *last = *syntax_errors;
           if (last == NULL) *syntax_errors = e; else
             {
@@ -1730,6 +1730,7 @@ parse_message_id(uschar *str, uschar **yield, uschar **error)
 {
 uschar *domain = NULL;
 uschar *id;
+rmark reset_point;


str = skip_comment(str);
if (*str != '<')
@@ -1742,27 +1743,28 @@ if (*str != '<')
for the answer, but it may also be very long if we are processing a header
line. Therefore, take care to release unwanted store afterwards. */

-id = *yield = store_get(Ustrlen(str) + 1);
+reset_point = store_mark();
+id = *yield = store_get(Ustrlen(str) + 1, is_tainted(str));
*id++ = *str++;

str = read_addr_spec(str, id, '>', error, &domain);

-if (*error == NULL)
+if (!*error)
   {
   if (*str != '>') *error = US"Missing '>' after message-id";
     else if (domain == NULL) *error = US"domain missing in message-id";
   }


-if (*error != NULL)
+if (*error)
{
- store_reset(*yield);
+ store_reset(reset_point);
return NULL;
}

-while (*id != 0) id++;
+while (*id) id++;
*id++ = *str++;
*id++ = 0;
-store_reset(id);
+store_release_above(id);

str = skip_comment(str);
return str;
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 9ebcfc1..239532b 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -238,7 +238,7 @@ debug_printf("\n");
static pdkim_stringlist *
pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
{
-pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist));
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE);

 memset(new_entry, 0, sizeof(pdkim_stringlist));
 new_entry->value = string_copy(str);
@@ -328,7 +328,7 @@ pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
-uschar * relaxed = store_get(len+3);
+uschar * relaxed = store_get(len+3, TRUE);    /* tainted */
 uschar * q = relaxed;


for (const uschar * p = header; p - header < len; p++)
@@ -408,7 +408,7 @@ pdkim_decode_qp(const uschar * str)
int nchar = 0;
uschar * q;
const uschar * p = str;
-uschar * n = store_get(Ustrlen(str)+1);
+uschar * n = store_get(Ustrlen(str)+1, TRUE);

*n = '\0';
q = n;
@@ -465,7 +465,7 @@ BOOL past_hname = FALSE;
BOOL in_b_val = FALSE;
int where = PDKIM_HDR_LIMBO;

-sig = store_get(sizeof(pdkim_signature));
+sig = store_get(sizeof(pdkim_signature), FALSE);
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;

@@ -474,7 +474,7 @@ sig->version = 0;
sig->keytype = -1;
sig->hashtype = -1;

-q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE);    /* tainted */


for (uschar * p = raw_hdr; ; p++)
{
@@ -656,7 +656,7 @@ const uschar * ele;
int sep = ';';
pdkim_pubkey * pub;

-pub = store_get(sizeof(pdkim_pubkey));
+pub = store_get(sizeof(pdkim_pubkey), TRUE);    /* tainted */
 memset(pub, 0, sizeof(pdkim_pubkey));


while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
@@ -1865,11 +1865,12 @@ pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing)
{
pdkim_ctx * ctx;

-ctx = store_get(sizeof(pdkim_ctx));
+ctx = store_get(sizeof(pdkim_ctx), FALSE);
memset(ctx, 0, sizeof(pdkim_ctx));

if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+/* The line-buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
ctx->dns_txt_callback = dns_txt_callback;

return ctx;
@@ -1891,7 +1892,7 @@ if (!domain || !selector || !privkey)

/* Allocate & init one signature struct */

-sig = store_get(sizeof(pdkim_signature));
+sig = store_get(sizeof(pdkim_signature), FALSE);
memset(sig, 0, sizeof(pdkim_signature));

sig->bodylength = -1;
@@ -1977,7 +1978,7 @@ for (b = ctx->bodyhash; b; b = b->next)

 DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n",
                   hashtype, canon_method, bodylength);
-b = store_get(sizeof(pdkim_bodyhash));
+b = store_get(sizeof(pdkim_bodyhash), FALSE);
 b->next = ctx->bodyhash;
 b->hashtype = hashtype;
 b->canon_method = canon_method;
@@ -2021,7 +2022,8 @@ pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
 {
 memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+/* The line buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
 DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
 }


diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c
index 53a8a7b..de64ee4 100644
--- a/src/src/pdkim/signing.c
+++ b/src/src/pdkim/signing.c
@@ -499,7 +499,7 @@ switch (hash)
}

#define SIGSPACE 128
-sig->data = store_get(SIGSPACE);
+sig->data = store_get(SIGSPACE, FALSE);

 if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
   {
@@ -755,7 +755,7 @@ switch (hash)
 if (  (ctx = EVP_MD_CTX_new())
    && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
    && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
-   && (sig->data = store_get(siglen))
+   && (sig->data = store_get(siglen, FALSE))


    /* Obtain the signature (slen could change here!) */
    && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
@@ -771,7 +771,7 @@ if (  (ctx = EVP_MD_CTX_create())
    && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
    && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
    && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
-   && (sig->data = store_get(siglen))
+   && (sig->data = store_get(siglen, FALSE))


    /* Obtain the signature (slen could change here!) */
    && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
diff --git a/src/src/queue.c b/src/src/queue.c
index f54124c..a124782 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -239,7 +239,7 @@ for (; i <= *subcount; i++)
         Ustrcmp(name + SPOOL_NAME_LENGTH - 2, "-H") == 0)
       {
       queue_filename *next =
-        store_get(sizeof(queue_filename) + Ustrlen(name));
+        store_get(sizeof(queue_filename) + Ustrlen(name), is_tainted(name));
       Ustrcpy(next->text, name);
       next->dir_uschar = subdirchar;


@@ -457,7 +457,7 @@ for (int i = queue_run_in_order ? -1 : 0;
      i <= (queue_run_in_order ? -1 : subcount);
      i++)
   {
-  void *reset_point1 = store_get(0);
+  rmark reset_point1 = store_mark();


   DEBUG(D_queue_run)
     {
@@ -521,7 +521,7 @@ for (int i = queue_run_in_order ? -1 : 0;
       {
       BOOL wanted = TRUE;
       BOOL orig_dont_deliver = f.dont_deliver;
-      void *reset_point2 = store_get(0);
+      rmark reset_point2 = store_mark();


       /* Restore the original setting of dont_deliver after reading the header,
       so that a setting for a particular message doesn't force it for any that
@@ -647,7 +647,7 @@ for (int i = queue_run_in_order ? -1 : 0;
       if (f.running_in_test_harness) millisleep(100);
       (void)close(pfd[pipe_read]);
       rc = deliver_message(fq->text, force_delivery, FALSE);
-      _exit(rc == DELIVER_NOT_ATTEMPTED);
+      exim_underbar_exit(rc == DELIVER_NOT_ATTEMPTED);
       }
     if (pid < 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork of delivery process from "
@@ -816,7 +816,7 @@ queue_list(int option, uschar **list, int count)
 {
 int subcount;
 int now = (int)time(NULL);
-void *reset_point;
+rmark reset_point;
 queue_filename * qf = NULL;
 uschar subdirs[64];


@@ -828,7 +828,7 @@ if (count > 0)
   for (int i = 0; i < count; i++)
     {
     queue_filename *next =
-      store_get(sizeof(queue_filename) + Ustrlen(list[i]) + 2);
+      store_get(sizeof(queue_filename) + Ustrlen(list[i]) + 2, is_tainted(list[i]));
     sprintf(CS next->text, "%s-H", list[i]);
     next->dir_uschar = '*';
     next->next = NULL;
@@ -851,8 +851,8 @@ if (option >= 8) option -= 8;
 /* Now scan the chain and print information, resetting store used
 each time. */


-for (reset_point = store_get(0);
-    qf;
+for (;
+    qf && (reset_point = store_mark());
     spool_clear_header_globals(), store_reset(reset_point), qf = qf->next
     )
   {
diff --git a/src/src/rda.c b/src/src/rda.c
index 228f574..42b7b14 100644
--- a/src/src/rda.c
+++ b/src/src/rda.c
@@ -96,37 +96,37 @@ static int
 rda_exists(uschar *filename, uschar **error)
 {
 int rc, saved_errno;
-uschar *slash;
 struct stat statbuf;
+uschar * s;


if ((rc = Ustat(filename, &statbuf)) >= 0) return FILE_EXIST;
saved_errno = errno;

-Ustrncpy(big_buffer, filename, big_buffer_size - 3);
+s = string_copy(filename);
sigalrm_seen = FALSE;

if (saved_errno == ENOENT)
{
- slash = Ustrrchr(big_buffer, '/');
- Ustrcpy(slash+1, ".");
+ uschar * slash = Ustrrchr(s, '/');
+ Ustrcpy(slash+1, US".");

   ALARM(30);
-  rc = Ustat(big_buffer, &statbuf);
+  rc = Ustat(s, &statbuf);
   if (rc != 0 && errno == EACCES && !sigalrm_seen)
     {
     *slash = 0;
-    rc = Ustat(big_buffer, &statbuf);
+    rc = Ustat(s, &statbuf);
     }
   saved_errno = errno;
   ALARM_CLR(0);


- DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
+ DEBUG(D_route) debug_printf("stat(%s)=%d\n", s, rc);
}

 if (sigalrm_seen || rc != 0)
   {
-  *error = string_sprintf("failed to stat %s (%s)", big_buffer,
-    sigalrm_seen? "timeout" : strerror(saved_errno));
+  *error = string_sprintf("failed to stat %s (%s)", s,
+    sigalrm_seen?  "timeout" : strerror(saved_errno));
   return FILE_EXIST_UNCLEAR;
   }


@@ -281,7 +281,7 @@ if (statbuf.st_size > MAX_FILTER_SIZE)

/* Read the file in one go in order to minimize the time we have it open. */

-filebuf = store_get(statbuf.st_size + 1);
+filebuf = store_get(statbuf.st_size + 1, is_tainted(filename));

if (fread(filebuf, 1, statbuf.st_size, fwd) != statbuf.st_size)
{
@@ -366,7 +366,7 @@ if (*filtertype != FILTER_FORWARD)
int old_expand_forbid = expand_forbid;

   DEBUG(D_route) debug_printf("data is %s filter program\n",
-    (*filtertype == FILTER_EXIM)? "an Exim" : "a Sieve");
+    *filtertype == FILTER_EXIM ? "an Exim" : "a Sieve");


/* RDO_FILTER is an "allow" bit */

@@ -377,8 +377,7 @@ if (*filtertype != FILTER_FORWARD)
     }


   expand_forbid =
-    (expand_forbid & ~RDO_FILTER_EXPANSIONS) |
-    (options & RDO_FILTER_EXPANSIONS);
+    expand_forbid & ~RDO_FILTER_EXPANSIONS  |  options & RDO_FILTER_EXPANSIONS;


/* RDO_{EXIM,SIEVE}_FILTER are forbid bits */

@@ -473,7 +472,8 @@ if (len == 0)
else
/* We know we have enough memory so disable the error on "len" */
/* coverity[tainted_data] */
- if (read(fd, *sp = store_get(len), len) != len) return FALSE;
+ /* We trust the data source, so untainted */
+ if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE;
return TRUE;
}

@@ -552,13 +552,12 @@ uschar *data;
uschar *readerror = US"";
void (*oldsignal)(int);

-DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n",
- (rdata->isfile)? "file" : "string", rdata->string);
+DEBUG(D_route) debug_printf("rda_interpret (%s): '%s'\n",
+ rdata->isfile ? "file" : "string", string_printing(rdata->string));

/* Do the expansions of the file name or data first, while still privileged. */

-data = expand_string(rdata->string);
-if (data == NULL)
+if (!(data = expand_string(rdata->string)))
{
if (f.expand_string_forcedfail) return FF_NOTDELIVERED;
*error = string_sprintf("failed to expand \"%s\": %s", rdata->string,
@@ -567,7 +566,7 @@ if (data == NULL)
}
rdata->string = data;

-DEBUG(D_route) debug_printf("expanded: %s\n", data);
+DEBUG(D_route) debug_printf("expanded: '%s'\n", data);

if (rdata->isfile && data[0] != '/')
{
@@ -767,7 +766,7 @@ if ((pid = fork()) == 0)
out:
(void)close(fd);
search_tidyup();
- _exit(0);
+ exim_underbar_exit(0);

 bad:
   DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n");
@@ -802,7 +801,7 @@ if (eblockp)
     uschar *s;
     if (!rda_read_string(fd, &s)) goto DISASTER;
     if (!s) break;
-    e = store_get(sizeof(error_block));
+    e = store_get(sizeof(error_block), FALSE);
     e->next = NULL;
     e->text1 = s;
     if (!rda_read_string(fd, &s)) goto DISASTER;
@@ -866,7 +865,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
     /* First string is the address; NULL => end of addresses */


     if (!rda_read_string(fd, &recipient)) goto DISASTER;
-    if (recipient == NULL) break;
+    if (!recipient) break;


     /* Hang on the end of the chain */


@@ -901,7 +900,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||

     if (i > 0)
       {
-      addr->pipe_expandn = store_get((i+1) * sizeof(uschar *));
+      addr->pipe_expandn = store_get((i+1) * sizeof(uschar *), FALSE);
       addr->pipe_expandn[i] = NULL;
       while (--i >= 0) addr->pipe_expandn[i] = expandn[i];
       }
@@ -911,7 +910,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
     if (read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER;
     if ((reply_options & REPLY_EXISTS) != 0)
       {
-      addr->reply = store_get(sizeof(reply_item));
+      addr->reply = store_get(sizeof(reply_item), FALSE);


       addr->reply->file_expand = (reply_options & REPLY_EXPAND) != 0;
       addr->reply->return_message = (reply_options & REPLY_RETURN) != 0;
diff --git a/src/src/readconf.c b/src/src/readconf.c
index a7cf03d..52c6483 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -630,7 +630,7 @@ Args:
 macro_item *
 macro_create(const uschar * name, const uschar * val, BOOL command_line)
 {
-macro_item * m = store_get(sizeof(macro_item));
+macro_item * m = store_get(sizeof(macro_item), FALSE);


READCONF_DEBUG fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val);
m->next = NULL;
@@ -1061,7 +1061,7 @@ for (;;)

     if (config_lines)
       save_config_position(config_filename, config_lineno);
-    save = store_get(sizeof(config_file_item));
+    save = store_get(sizeof(config_file_item), FALSE);
     save->next = config_file_stack;
     config_file_stack = save;
     save->file = config_file;
@@ -1401,7 +1401,7 @@ Returns:      the control block for the parsed rule.
 static rewrite_rule *
 readconf_one_rewrite(const uschar *p, int *existflags, BOOL isglobal)
 {
-rewrite_rule *next = store_get(sizeof(rewrite_rule));
+rewrite_rule *next = store_get(sizeof(rewrite_rule), FALSE);


next->next = NULL;
next->key = string_dequote(&p);
@@ -1603,7 +1603,7 @@ BOOL boolvalue = TRUE;
BOOL freesptr = TRUE;
optionlist *ol, *ol2;
struct passwd *pw;
-void *reset_point;
+rmark reset_point;
int intbase = 0;
uschar *inttype = US"";
uschar *sptr;
@@ -1727,7 +1727,8 @@ switch (type)
case opt_gidlist:
case opt_rewrite:

- reset_point = sptr = read_string(s, name);
+ reset_point = store_mark();
+ sptr = read_string(s, name);

   /* Having read a string, we now have several different ways of using it,
   depending on the data type, so do another switch. If keeping the actual
@@ -1750,10 +1751,11 @@ switch (type)
       /* We already have a condition, we're conducting a crude hack to let
       multiple condition rules be chained together, despite storing them in
       text form. */
-      *str_target = string_copy_malloc( (saved_condition = *str_target)
+      *str_target = string_copy_perm( (saved_condition = *str_target)
     ? string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
         saved_condition, sptr)
-    : sptr);
+    : sptr,
+    FALSE);
       /* TODO(pdp): there is a memory leak here and just below
       when we set 3 or more conditions; I still don't
       understand the store mechanism enough to know
@@ -1789,7 +1791,7 @@ switch (type)
     list_o = string_append_listele(list_o, sep_o, s);


       if (list_o)
-    *str_target = string_copy_malloc(string_from_gstring(list_o));
+    *str_target = string_copy_perm(string_from_gstring(list_o), FALSE);
       }
     else
       {
@@ -1891,7 +1893,7 @@ switch (type)
     ignore. Also ignore if the value is already set. */


     if (pw == NULL) break;
-    Ustrcpy(name+Ustrlen(name)-4, "group");
+    Ustrcpy(name+Ustrlen(name)-4, US"group");
     ol2 = find_option(name, oltop, last);
     if (ol2 != NULL && ((ol2->type & opt_mask) == opt_gid ||
         (ol2->type & opt_mask) == opt_expand_gid))
@@ -2031,7 +2033,7 @@ switch (type)


/* Release store if the value of the string doesn't need to be kept. */

- if (freesptr) store_reset(reset_point);
+ if (freesptr) reset_point = store_reset(reset_point);
break;

   /* Expanded boolean: if no characters follow, or if there are no dollar
@@ -2042,10 +2044,10 @@ switch (type)
   if (*s != 0 && Ustrchr(s, '$') != 0)
     {
     sprintf(CS name2, "*expand_%.50s", name);
-    ol2 = find_option(name2, oltop, last);
-    if (ol2 != NULL)
+    if ((ol2 = find_option(name2, oltop, last)))
       {
-      reset_point = sptr = read_string(s, name);
+      reset_point = store_mark();
+      sptr = read_string(s, name);
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = sptr;
       else
@@ -2983,7 +2985,7 @@ read_named_list(tree_node **anchorp, int *numberp, int max, uschar *s,
 BOOL forcecache = FALSE;
 uschar *ss;
 tree_node *t;
-namedlist_block *nb = store_get(sizeof(namedlist_block));
+namedlist_block *nb = store_get(sizeof(namedlist_block), FALSE);


 if (Ustrncmp(s, "_cache", 6) == 0)
   {
@@ -3001,7 +3003,7 @@ if (*numberp >= max)
 while (isspace(*s)) s++;
 ss = s;
 while (isalnum(*s) || *s == '_') s++;
-t = store_get(sizeof(tree_node) + s-ss);
+t = store_get(sizeof(tree_node) + s-ss, is_tainted(ss));
 Ustrncpy(t->name, ss, s-ss);
 t->name[s-ss] = 0;
 while (isspace(*s)) s++;
@@ -3123,7 +3125,7 @@ if (pid == 0)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
         "tls_require_ciphers invalid: %s", errmsg);
   fflush(NULL);
-  _exit(0);
+  exim_underbar_exit(0);
   }


 do {
@@ -3326,6 +3328,19 @@ if (f.trusted_config && Ustrcmp(filename, US"/dev/null"))
       "wrong owner, group, or mode", big_buffer);
   }


+/* Do a dummy store-allocation of a size related to the (toplevel) file size.
+This assumes we will need this much storage to handle all the allocations
+during startup; it won't help when .include is being used.  When it does, it
+will cut down on the number of store blocks (and malloc calls, and sbrk
+syscalls).  It also assume we're on the relevant pool. */
+
+if (statbuf.st_size > 8192)
+  {
+  rmark r = store_mark();
+  store_get((int)statbuf.st_size, FALSE);
+  store_reset(r);
+  }
+
 /* Process the main configuration settings. They all begin with a lower case
 letter. If we see something starting with an upper case letter, it is taken as
 a macro definition. */
@@ -3697,7 +3712,7 @@ for (driver_info * dd = drivers_available; dd->driver_name[0] != 0;
     {
     int len = dd->options_len;
     d->info = dd;
-    d->options_block = store_get(len);
+    d->options_block = store_get(len, FALSE);
     memcpy(d->options_block, dd->options_block, len);
     for (int i = 0; i < *(dd->options_count); i++)
       dd->options[i].type &= ~opt_set;
@@ -3809,7 +3824,7 @@ while ((buffer = get_config_line()) != NULL)
     /* Set up a new driver instance data block on the chain, with
     its default values installed. */


-    d = store_get(instance_size);
+    d = store_get(instance_size, FALSE);
     memcpy(d, instance_default, instance_size);
     *p = d;
     p = &d->next;
@@ -4108,7 +4123,7 @@ while ((p = get_config_line()))
   const uschar *pp;
   uschar *error;


- next = store_get(sizeof(retry_config));
+ next = store_get(sizeof(retry_config), FALSE);
next->next = NULL;
*chain = next;
chain = &(next->next);
@@ -4152,7 +4167,7 @@ while ((p = get_config_line()))

   while (*p != 0)
     {
-    retry_rule *rule = store_get(sizeof(retry_rule));
+    retry_rule *rule = store_get(sizeof(retry_rule), FALSE);
     *rchain = rule;
     rchain = &(rule->next);
     rule->next = NULL;
@@ -4302,7 +4317,7 @@ while(acl_line)
   if (*p != ':' || name[0] == 0)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing or malformed ACL name");


-  node = store_get(sizeof(tree_node) + Ustrlen(name));
+  node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name));
   Ustrcpy(node->name, name);
   if (!tree_insertnode(&acl_anchor, node))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
@@ -4390,7 +4405,7 @@ while(next_section[0] != 0)
   int mid = last/2;
   int n = Ustrlen(next_section);


- if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, "s");
+ if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, US"s");

   for (;;)
     {
@@ -4449,7 +4464,7 @@ save_config_line(const uschar* line)
 static config_line_item *current;
 config_line_item *next;


-next = (config_line_item*) store_get(sizeof(config_line_item));
+next = (config_line_item*) store_get(sizeof(config_line_item), FALSE);
next->line = string_copy(line);
next->next = NULL;

diff --git a/src/src/receive.c b/src/src/receive.c
index ed2afb3..c7553f8 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -489,7 +489,7 @@ if (recipients_count >= recipients_list_max)
   recipient_item *oldlist = recipients_list;
   int oldmax = recipients_list_max;
   recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
-  recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
+  recipients_list = store_get(recipients_list_max * sizeof(recipient_item), FALSE);
   if (oldlist != NULL)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   }
@@ -1677,6 +1677,7 @@ uschar *frozen_by = NULL;
 uschar *queued_by = NULL;


uschar *errmsg;
+rmark rcvd_log_reset_point;
gstring * g;
struct stat statbuf;

@@ -1687,6 +1688,7 @@ uschar *user_msg, *log_msg;

/* Working header pointers */

+rmark reset_point;
header_line *next;

/* Flags for noting the existence of certain headers (only one left) */
@@ -1727,7 +1729,7 @@ if (extract_recip || !smtp_input)
header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
pointing to the end of the chain to make adding headers simple. */

-received_header = header_list = header_last = store_get(sizeof(header_line));
+received_header = header_list = header_last = store_get(sizeof(header_line), FALSE);
header_list->next = NULL;
header_list->type = htype_old;
header_list->text = NULL;
@@ -1735,8 +1737,9 @@ header_list->slen = 0;

/* Control block for the next header to be read. */

-next = store_get(sizeof(header_line));
-next->text = store_get(header_size);
+reset_point = store_mark();
+next = store_get(sizeof(header_line), FALSE);    /* not tainted */
+next->text = store_get(header_size, TRUE);    /* tainted */


 /* Initialize message id to be null (indicating no message read), and the
 header names list to be the normal list. Indicate there is no data file open
@@ -1854,8 +1857,10 @@ for (;;)
       goto OVERSIZE;
     header_size *= 2;


-    if (!store_extend(next->text, oldsize, header_size))
-      next->text = store_newblock(next->text, header_size, ptr);
+    /* The data came from the message, so is tainted. */
+
+    if (!store_extend(next->text, TRUE, oldsize, header_size))
+      next->text = store_newblock(next->text, TRUE, header_size, ptr);
     }


   /* Cope with receiving a binary zero. There is dispute about whether
@@ -1910,7 +1915,7 @@ for (;;)
     if (ch == '\n')
       {
       message_ended = END_DOT;
-      store_reset(next);
+      reset_point = store_reset(reset_point);
       next = NULL;
       break;                    /* End character-reading loop */
       }
@@ -2016,7 +2021,7 @@ OVERSIZE:


   if (ptr == 1)
     {
-    store_reset(next);
+    reset_point = store_reset(reset_point);
     next = NULL;
     break;
     }
@@ -2045,7 +2050,7 @@ OVERSIZE:


next->text[ptr] = 0;
next->slen = ptr;
- store_reset(next->text + ptr + 1);
+ store_release_above(next->text + ptr + 1);

/* Check the running total size against the overall message size limit. We
don't expect to fail here, but if the overall limit is set less than MESSAGE_
@@ -2241,9 +2246,10 @@ OVERSIZE:

/* Set up for the next header */

+  reset_point = store_mark();
   header_size = 256;
-  next = store_get(sizeof(header_line));
-  next->text = store_get(header_size);
+  next = store_get(sizeof(header_line), FALSE);
+  next->text = store_get(header_size, TRUE);
   ptr = 0;
   had_zero = 0;
   prevlines_length = 0;
@@ -2527,7 +2533,7 @@ if (extract_recip)
         white space that follows the newline must not be removed - it is part
         of the header. */


-        pp = recipient = store_get(ss - s + 1);
+        pp = recipient = store_get(ss - s + 1, is_tainted(s));
         for (uschar * p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;


@@ -2558,7 +2564,7 @@ if (extract_recip)
         if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
           {
           int len = Ustrlen(s);
-          error_block *b = store_get(sizeof(error_block));
+          error_block *b = store_get(sizeof(error_block), FALSE);
           while (len > 0 && isspace(s[len-1])) len--;
           b->next = NULL;
           b->text1 = string_printing(string_copyn(s, len));
@@ -2765,7 +2771,7 @@ function may mess with the real recipients. */


 if (LOGGING(received_recipients))
   {
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
+  raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
   for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = string_copy(recipients_list[i].address);
   raw_recipients_count = recipients_count;
@@ -3968,6 +3974,7 @@ it first! Include any message id that is in the message - since the syntax of a
 message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */


+rcvd_log_reset_point = store_mark();
g = string_get(256);

g = string_append(g, 2,
@@ -4231,7 +4238,7 @@ if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)

     case '4':    /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
               ... for which, pass back the exact error */
-      if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
+      if (cutthrough.defer_pass) smtp_reply = string_copy_perm(msg, TRUE);
       cutthrough_done = TMP_REJ;        /* Avoid the usual immediate delivery attempt */
       break;                    /* message_id needed for SMTP accept below */


@@ -4241,7 +4248,7 @@ if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
       break;                    /* message_id needed for SMTP accept below */


     case '5':    /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
-      smtp_reply = string_copy_malloc(msg);        /* Pass on the exact error */
+      smtp_reply = string_copy_perm(msg, TRUE);        /* Pass on the exact error */
       cutthrough_done = PERM_REJ;
       break;
     }
@@ -4268,7 +4275,8 @@ if(!smtp_reply)
   }
 f.receive_call_bombout = FALSE;


-store_reset(g); /* The store for the main log message can be reused */
+/* The store for the main log message can be reused */
+rcvd_log_reset_point = store_reset(rcvd_log_reset_point);

/* If the message is frozen, and freeze_tell is set, do the telling. */

diff --git a/src/src/regex.c b/src/src/regex.c
index 87d03f3..f9c06b9 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -53,7 +53,7 @@ while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
       continue;
       }


-    ri = store_get(sizeof(pcre_list));
+    ri = store_get(sizeof(pcre_list), FALSE);
     ri->re = re;
     ri->pcre_text = regex_string;
     ri->next = re_list_head;
@@ -125,7 +125,7 @@ if (!(re_list_head = compile(*listptr)))
   return FAIL;            /* no regexes -> nothing to do */


 /* match each line against all regexes */
-linebuffer = store_get(32767);
+linebuffer = store_get(32767, TRUE);    /* tainted */
 while (fgets(CS linebuffer, 32767, mbox_file))
   {
   if (  mime_stream && mime_current_boundary        /* check boundary */
@@ -195,8 +195,8 @@ if (!(f = fopen(CS mime_decoded_filename, "rb")))
   return DEFER;
   }


-/* get 32k memory */
-mime_subject = store_get(32767);
+/* get 32k memory, tainted */
+mime_subject = store_get(32767, TRUE);

mime_subject_len = fread(mime_subject, 1, 32766, f);

diff --git a/src/src/retry.c b/src/src/retry.c
index 509de12..d068f54 100644
--- a/src/src/retry.c
+++ b/src/src/retry.c
@@ -291,7 +291,7 @@ Returns: nothing
void
retry_add_item(address_item *addr, uschar *key, int flags)
{
-retry_item *rti = store_get(sizeof(retry_item));
+retry_item *rti = store_get(sizeof(retry_item), FALSE);
host_item * host = addr->host_used;

rti->next = addr->retries;
@@ -666,7 +666,8 @@ for (int i = 0; i < 3; i++)

         if (!retry_record)
           {
-          retry_record = store_get(sizeof(dbdata_retry) + message_length);
+          retry_record = store_get(sizeof(dbdata_retry) + message_length,
+                   is_tainted(message));
           message_space = message_length;
           retry_record->first_failed = now;
           retry_record->last_try = now;
@@ -810,7 +811,7 @@ for (int i = 0; i < 3; i++)


         if (message_length > message_space)
           {
-          dbdata_retry *newr = store_get(sizeof(dbdata_retry) + message_length);
+          dbdata_retry *newr = store_get(sizeof(dbdata_retry) + message_length, FALSE);
           memcpy(newr, retry_record, sizeof(dbdata_retry));
           retry_record = newr;
           }
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index 221b438..f942bec 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -454,7 +454,7 @@ rewrite_one_header(header_line *h, int flag,
 {
 int lastnewline = 0;
 header_line *newh = NULL;
-void *function_reset_point = store_get(0);
+rmark function_reset_point = store_mark();
 uschar *s = Ustrchr(h->text, ':') + 1;
 while (isspace(*s)) s++;


@@ -475,7 +475,7 @@ while (*s)
uschar *sprev;
uschar *ss = parse_find_address_end(s, FALSE);
uschar *recipient, *new, *errmess;
- void *loop_reset_point = store_get(0);
+ rmark loop_reset_point = store_mark();
BOOL changed = FALSE;
int terminator = *ss;
int start, end, domain;
@@ -496,7 +496,7 @@ while (*s)

   if (!recipient)
     {
-    store_reset(loop_reset_point);
+    loop_reset_point = store_reset(loop_reset_point);
     continue;
     }


@@ -543,7 +543,7 @@ while (*s)
     if (changed && ((is_recipient && !f.allow_unqualified_recipient) ||
                     (!is_recipient && !f.allow_unqualified_sender)))
       {
-      store_reset(loop_reset_point);
+      loop_reset_point = store_reset(loop_reset_point);
       continue;
       }
     }
@@ -575,7 +575,7 @@ while (*s)
   point, because we may have a rewritten line from a previous time round the
   loop. */


- if (!changed) store_reset(loop_reset_point);
+ if (!changed) loop_reset_point = store_reset(loop_reset_point);

   /* If the address has changed, create a new header containing the
   rewritten address. We do not need to set the chain pointers at this
@@ -592,9 +592,9 @@ while (*s)
     int newlen = Ustrlen(new);
     int oldlen = end - start;


-    header_line *prev = (newh == NULL)? h : newh;
-    uschar *newt = store_malloc(prev->slen - oldlen + newlen + 4);
-    uschar *newtstart = newt;
+    header_line * prev = newh ? newh : h;
+    uschar * newt = store_get_perm(prev->slen - oldlen + newlen + 4, TRUE);
+    uschar * newtstart = newt;


     int type = prev->type;
     int slen = prev->slen - oldlen + newlen;
@@ -633,7 +633,7 @@ while (*s)
       if (*p != '\n')
         {
         lastnewline = newt - newtstart;
-        Ustrcat(newt, "\n\t");
+        Ustrcat(newt, US"\n\t");
         slen += 2;
         }
       }
@@ -656,11 +656,11 @@ while (*s)
     rewritten copy from a previous time round this loop. */


     store_reset(function_reset_point);
-    newh = store_get(sizeof(header_line));
+    function_reset_point = store_mark();
+    newh = store_get(sizeof(header_line), FALSE);
     newh->type = type;
     newh->slen = slen;
     newh->text = string_copyn(newtstart, slen);
-    store_free(newtstart);


     /* Set up for scanning the rest of the header */


diff --git a/src/src/rfc2047.c b/src/src/rfc2047.c
index 5d527d4..f708500 100644
--- a/src/src/rfc2047.c
+++ b/src/src/rfc2047.c
@@ -46,7 +46,7 @@ rfc2047_qpdecode(uschar *string, uschar **ptrptr)
int len = 0;
uschar *ptr;

-ptr = *ptrptr = store_get(Ustrlen(string) + 1); /* No longer than this */
+ptr = *ptrptr = store_get(Ustrlen(string) + 1, is_tainted(string)); /* No longer than this */

while (*string != 0)
{
@@ -208,7 +208,7 @@ building the result as we go. The result may be longer than the input if it is
translated into a multibyte code such as UTF-8. That's why we use the dynamic
string building code. */

-yield = store_get(sizeof(gstring) + ++size);
+yield = store_get(sizeof(gstring) + ++size, is_tainted(string));
yield->size = size;
yield->ptr = 0;
yield->s = US(yield + 1);
@@ -222,6 +222,7 @@ while (mimeword)

   if (mimeword != string)
     yield = string_catn(yield, string, mimeword - string);
+/*XXX that might have to convert an untainted string to a tainted one */


   /* Do a charset translation if required. This is supported only on hosts
   that have the iconv() function. Translation errors set error, but carry on,
diff --git a/src/src/route.c b/src/src/route.c
index b693049..41716bc 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -737,21 +737,20 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
       {
       exim_setugid(uid, gid, TRUE,
         string_sprintf("require_files check, file=%s", ss));
-      if (route_check_access(ss, uid, gid, 4)) _exit(0);
+      if (route_check_access(ss, uid, gid, 4))
+    exim_underbar_exit(0);
       DEBUG(D_route) debug_printf("route_check_access() failed\n");
-      _exit(1);
+      exim_underbar_exit(1);
       }


     /* In the parent, wait for the child to finish */


     while (waitpid(pid, &status, 0) < 0)
-     {
      if (errno != EINTR)  /* unexpected error, interpret as failure */
        {
        status = 1;
        break;
        }
-     }


     signal(SIGCHLD, oldsignal);   /* restore */
     if ((status == 0) == invert) return SKIP;
@@ -1101,7 +1100,7 @@ route_finduser(const uschar *s, struct passwd **pw, uid_t *return_uid)
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);


DEBUG(D_uid) debug_printf("seeking password data for user \"%s\": %s\n", s,
- cache_set? "using cached result" : "cache not available");
+ cache_set ? "using cached result" : "cache not available");

 if (!cache_set)
   {
@@ -1115,7 +1114,7 @@ if (!cache_set)
     return TRUE;
     }


- (void)string_format(lastname, sizeof(lastname), "%s", s);
+ string_format_nt(lastname, sizeof(lastname), "%s", s);

/* Force failure if string length is greater than given maximum */

@@ -1474,13 +1473,15 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
       }


   if (!(node = tree_search(*root, name)))
-    {
-    node = store_get(sizeof(tree_node) + Ustrlen(name));
+    {                /* name should never be tainted */
+    node = store_get(sizeof(tree_node) + Ustrlen(name), FALSE);
     Ustrcpy(node->name, name);
     (void)tree_insertnode(root, node);
     }
   node->data.ptr = US val;
-  DEBUG(D_route) debug_printf("set r_%s = '%s'\n", name, val);
+  DEBUG(D_route) debug_printf("set r_%s%s = '%s'%s\n",
+            name, is_tainted(name)?" (tainted)":"",
+            val, is_tainted(val)?" (tainted)":"");


/* All expansions after this point need visibility of that variable */
router_var = *root;
@@ -1789,9 +1790,10 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
/* If succeeded while verifying but fail_verify is set, convert into
a failure, and take it off the local or remote delivery list. */

-  if (((verify == v_sender && r->fail_verify_sender) ||
-       (verify == v_recipient && r->fail_verify_recipient)) &&
-      (yield == OK || yield == PASS))
+  if (  (  verify == v_sender && r->fail_verify_sender
+    || verify == v_recipient && r->fail_verify_recipient
+    )
+     && (yield == OK || yield == PASS))
     {
     addr->message = string_sprintf("%s router forced verify failure", r->name);
     if (*paddr_remote == addr) *paddr_remote = addr->next;
@@ -1808,7 +1810,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   HDEBUG(D_route)
     {
     debug_printf("%s router %s for %s\n", r->name,
-      (yield == PASS)? "passed" : "declined", addr->address);
+      yield == PASS ? "passed" : "declined", addr->address);
     if (Ustrcmp(old_domain, addr->domain) != 0)
       debug_printf("domain %s rewritten\n", old_domain);
     }
@@ -1872,12 +1874,8 @@ if (!r)


 if (yield == DEFER)
   {
-  HDEBUG(D_route)
-    {
-    debug_printf("%s router: defer for %s\n", r->name, addr->address);
-    debug_printf("  message: %s\n", (addr->message == NULL)?
-      US"<none>" : addr->message);
-    }
+  HDEBUG(D_route) debug_printf("%s router: defer for %s\n  message: %s\n",
+      r->name, addr->address, addr->message ? addr->message : US"<none>");
   goto ROUTE_EXIT;
   }


diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c
index 33939be..2471f2f 100644
--- a/src/src/routers/dnslookup.c
+++ b/src/src/routers/dnslookup.c
@@ -468,7 +468,7 @@ if (rc != OK) return rc;
/* Get store in which to preserve the original host item, chained on
to the address. */

-addr->host_list = store_get(sizeof(host_item));
+addr->host_list = store_get(sizeof(host_item), FALSE);
addr->host_list[0] = h;

/* Fill in the transport and queue the address for delivery. */
diff --git a/src/src/routers/ipliteral.c b/src/src/routers/ipliteral.c
index ecc6042..fb25e69 100644
--- a/src/src/routers/ipliteral.c
+++ b/src/src/routers/ipliteral.c
@@ -149,7 +149,7 @@ if (verify_check_this_host(CUSS&rblock->ignore_target_hosts,

/* Set up a host item */

-h = store_get(sizeof(host_item));
+h = store_get(sizeof(host_item), FALSE);

h->next = NULL;
h->address = string_copy(ip);
diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index 13849f9..4ceb1f5 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -160,7 +160,7 @@ uschar *reply;
uschar *hostname, *reroute, *domain;
const uschar *listptr;
uschar host_buffer[256];
-host_item *host = store_get(sizeof(host_item));
+host_item *host = store_get(sizeof(host_item), FALSE);
address_item *new_addr;
iplookup_router_options_block *ob =
(iplookup_router_options_block *)(rblock->options_block);
@@ -176,7 +176,7 @@ pw = pw;
DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
rblock->name, addr->address, addr->domain);

-reply = store_get(256);
+reply = store_get(256, TRUE);    /* tainted data */


 /* Build the query string to send. If not explicitly given, a default of
 "user@domain user@domain" is used. */
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index f7cca3a..c9ddd27 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -401,7 +401,7 @@ if (transport && transport->info->local)
   if (hostlist[0])
     {
     host_item *h;
-    addr->host_list = h = store_get(sizeof(host_item));
+    addr->host_list = h = store_get(sizeof(host_item), FALSE);
     h->name = string_copy(hostlist);
     h->address = NULL;
     h->port = PORT_NONE;
diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 09f15d0..6bbbf37 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -699,10 +699,10 @@ address. Otherwise, if a local qualify_domain is provided, set that up. */


if (ob->qualify_preserve_domain)
qualify_domain_recipient = addr->domain;
-else if (ob->qualify_domain != NULL)
+else if (ob->qualify_domain)
{
uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
- if (new_qdr == NULL) return xrc;
+ if (!new_qdr) return xrc;
qualify_domain_recipient = new_qdr;
}

@@ -713,16 +713,8 @@ redirect.check_owner = ob->check_owner;
redirect.check_group = ob->check_group;
redirect.pw = pw;

-if (ob->file != NULL)
- {
- redirect.string = ob->file;
- redirect.isfile = TRUE;
- }
-else
- {
- redirect.string = ob->data;
- redirect.isfile = FALSE;
- }
+redirect.string = (redirect.isfile = (ob->file != NULL))
+ ? ob->file : ob->data;

 frc = rda_interpret(&redirect, options, ob->include_directory,
   ob->sieve_vacation_directory, ob->sieve_enotify_mailto_owner,
@@ -738,104 +730,104 @@ For FAIL and FREEZE we honour any previously set up deliveries by a filter. */
 switch (frc)
   {
   case FF_NONEXIST:
-  addr->message = addr->user_message = NULL;
-  return DECLINE;
+    addr->message = addr->user_message = NULL;
+    return DECLINE;


   case FF_BLACKHOLE:
-  DEBUG(D_route) debug_printf("address :blackhole:d\n");
-  generated = NULL;
-  discarded = US":blackhole:";
-  frc = FF_DELIVERED;
-  break;
+    DEBUG(D_route) debug_printf("address :blackhole:d\n");
+    generated = NULL;
+    discarded = US":blackhole:";
+    frc = FF_DELIVERED;
+    break;


-  /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands
-  (:defer: or :fail: in an alias file or "fail" in a filter). If a configured
-  message was supplied, allow it to be included in an SMTP response after
-  verifying. Remove any SMTP code if it is not allowed. */
+    /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands
+    (:defer: or :fail: in an alias file or "fail" in a filter). If a configured
+    message was supplied, allow it to be included in an SMTP response after
+    verifying. Remove any SMTP code if it is not allowed. */


   case FF_DEFER:
-  yield = DEFER;
-  goto SORT_MESSAGE;
+    yield = DEFER;
+    goto SORT_MESSAGE;


   case FF_FAIL:
-  if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
-    return xrc;
-  add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
-  yield = FAIL;
+    if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
+      return xrc;
+    add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+    yield = FAIL;


-  SORT_MESSAGE:
-  if (addr->message == NULL)
-    addr->message = (yield == FAIL)? US"forced rejection" : US"forced defer";
-  else
-    {
-    int ovector[3];
-    if (ob->forbid_smtp_code &&
-        pcre_exec(regex_smtp_code, NULL, CS addr->message,
-          Ustrlen(addr->message), 0, PCRE_EOPT,
-          ovector, sizeof(ovector)/sizeof(int)) >= 0)
+    SORT_MESSAGE:
+    if (!addr->message)
+      addr->message = yield == FAIL ? US"forced rejection" : US"forced defer";
+    else
       {
-      DEBUG(D_route) debug_printf("SMTP code at start of error message "
-        "is ignored because forbid_smtp_code is set\n");
-      addr->message += ovector[1];
+      int ovector[3];
+      if (ob->forbid_smtp_code &&
+      pcre_exec(regex_smtp_code, NULL, CS addr->message,
+        Ustrlen(addr->message), 0, PCRE_EOPT,
+        ovector, sizeof(ovector)/sizeof(int)) >= 0)
+    {
+    DEBUG(D_route) debug_printf("SMTP code at start of error message "
+      "is ignored because forbid_smtp_code is set\n");
+    addr->message += ovector[1];
+    }
+      addr->user_message = addr->message;
+      setflag(addr, af_pass_message);
       }
-    addr->user_message = addr->message;
-    setflag(addr, af_pass_message);
-    }
-  return yield;
+    return yield;


-  /* As in the case of a system filter, a freeze does not happen after a manual
-  thaw. In case deliveries were set up by the filter, we set the child count
-  high so that their completion does not mark the original address done. */
+    /* As in the case of a system filter, a freeze does not happen after a manual
+    thaw. In case deliveries were set up by the filter, we set the child count
+    high so that their completion does not mark the original address done. */


   case FF_FREEZE:
-  if (!f.deliver_manual_thaw)
-    {
-    if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
-      != OK) return xrc;
-    add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
-    if (addr->message == NULL) addr->message = US"frozen by filter";
-    addr->special_action = SPECIAL_FREEZE;
-    addr->child_count = 9999;
-    return DEFER;
-    }
-  frc = FF_NOTDELIVERED;
-  break;
+    if (!f.deliver_manual_thaw)
+      {
+      if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
+    != OK) return xrc;
+      add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+      if (addr->message == NULL) addr->message = US"frozen by filter";
+      addr->special_action = SPECIAL_FREEZE;
+      addr->child_count = 9999;
+      return DEFER;
+      }
+    frc = FF_NOTDELIVERED;
+    break;


-  /* Handle syntax errors and :include: failures and lookup defers */
+    /* Handle syntax errors and :include: failures and lookup defers */


case FF_ERROR:
case FF_INCLUDEFAIL:

-  /* If filtertype is still FILTER_UNSET, it means that the redirection data
-  was never inspected, so the error was an expansion failure or failure to open
-  the file, or whatever. In these cases, the existing error message is probably
-  sufficient. */
+    /* If filtertype is still FILTER_UNSET, it means that the redirection data
+    was never inspected, so the error was an expansion failure or failure to open
+    the file, or whatever. In these cases, the existing error message is probably
+    sufficient. */


-  if (filtertype == FILTER_UNSET) return DEFER;
+    if (filtertype == FILTER_UNSET) return DEFER;


-  /* If it was a filter and skip_syntax_errors is set, we want to set up
-  the error message so that it can be logged and mailed to somebody. */
+    /* If it was a filter and skip_syntax_errors is set, we want to set up
+    the error message so that it can be logged and mailed to somebody. */


-  if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors)
-    {
-    eblock = store_get(sizeof(error_block));
-    eblock->next = NULL;
-    eblock->text1 = addr->message;
-    eblock->text2 = NULL;
-    addr->message = addr->user_message = NULL;
-    }
+    if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors)
+      {
+      eblock = store_get(sizeof(error_block), FALSE);
+      eblock->next = NULL;
+      eblock->text1 = addr->message;
+      eblock->text2 = NULL;
+      addr->message = addr->user_message = NULL;
+      }


-  /* Otherwise set up the error for the address and defer. */
+    /* Otherwise set up the error for the address and defer. */


-  else
-    {
-    addr->basic_errno = ERRNO_BADREDIRECT;
-    addr->message = string_sprintf("error in %s %s: %s",
-      (filtertype != FILTER_FORWARD)? "filter" : "redirect",
-      (ob->data == NULL)? "file" : "data",
-      addr->message);
-    return DEFER;
-    }
+    else
+      {
+      addr->basic_errno = ERRNO_BADREDIRECT;
+      addr->message = string_sprintf("error in %s %s: %s",
+    filtertype == FILTER_FORWARD ? "redirect" : "filter",
+    ob->data ? "data" : "file",
+    addr->message);
+      return DEFER;
+      }
   }



diff --git a/src/src/routers/rf_change_domain.c b/src/src/routers/rf_change_domain.c
index 71a6a10..39c41fc 100644
--- a/src/src/routers/rf_change_domain.c
+++ b/src/src/routers/rf_change_domain.c
@@ -35,7 +35,7 @@ void
 rf_change_domain(address_item *addr, const uschar *domain, BOOL rewrite,
   address_item **addr_new)
 {
-address_item *parent = store_get(sizeof(address_item));
+address_item *parent = store_get(sizeof(address_item), FALSE);
 uschar *at = Ustrrchr(addr->address, '@');
 uschar *address = string_sprintf("%.*s@%s",
   (int)(at - addr->address), addr->address, domain);
diff --git a/src/src/routers/rf_get_munge_headers.c b/src/src/routers/rf_get_munge_headers.c
index f08b55a..5c95d2d 100644
--- a/src/src/routers/rf_get_munge_headers.c
+++ b/src/src/routers/rf_get_munge_headers.c
@@ -59,7 +59,7 @@ if (rblock->extra_headers)
       shared with other addresses. The output function outputs them in reverse
       order. */


-      header_line *  h = store_get(sizeof(header_line));
+      header_line *  h = store_get(sizeof(header_line), FALSE);


       /* We used to use string_sprintf() to add the newline if needed, but that
       causes problems if the header line is exceedingly long (e.g. adding
@@ -69,7 +69,7 @@ if (rblock->extra_headers)
     h->text = s;
       else
     {
-    h->text = store_get(slen+2);
+    h->text = store_get(slen+2, is_tainted(s));
     memcpy(h->text, s, slen);
     h->text[slen++] = '\n';
     h->text[slen] = 0;
diff --git a/src/src/routers/rf_get_transport.c b/src/src/routers/rf_get_transport.c
index 353c470..4a43818 100644
--- a/src/src/routers/rf_get_transport.c
+++ b/src/src/routers/rf_get_transport.c
@@ -45,9 +45,9 @@ rf_get_transport(uschar *tpname, transport_instance **tpptr, address_item *addr,
 uschar *ss;
 BOOL expandable;


-if (tpname == NULL)
+if (!tpname)
   {
-  if (require_name == NULL) return TRUE;
+  if (!require_name) return TRUE;
   addr->basic_errno = ERRNO_BADTRANSPORT;
   addr->message = string_sprintf("%s unset in %s router", require_name,
     router_name);
@@ -59,16 +59,25 @@ if (*tpptr != NULL && !expandable) return TRUE;


 if (expandable)
   {
-  ss = expand_string(tpname);
-  if (ss == NULL)
+  if (!(ss = expand_string(tpname)))
     {
     addr->basic_errno = ERRNO_BADTRANSPORT;
     addr->message = string_sprintf("failed to expand transport "
       "\"%s\" in %s router: %s", tpname, router_name, expand_string_message);
     return FALSE;
     }
+  if (is_tainted(ss))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "attempt to use tainted value '%s' from '%s' for transport", ss, tpname);
+    addr->basic_errno = ERRNO_BADTRANSPORT;
+    /* Avoid leaking info to an attacker */
+    addr->message = US"internal configuration error";
+    return FALSE;
+    }
   }
-else ss = tpname;
+else
+  ss = tpname;


 for (transport_instance * tp = transports; tp; tp = tp->next)
   if (Ustrcmp(tp->name, ss) == 0)
diff --git a/src/src/routers/rf_queue_add.c b/src/src/routers/rf_queue_add.c
index 99de7b0..938eee3 100644
--- a/src/src/routers/rf_queue_add.c
+++ b/src/src/routers/rf_queue_add.c
@@ -96,7 +96,7 @@ DEBUG(D_route)
   {
   debug_printf("queued for %s transport: local_part = %s\ndomain = %s\n"
     "  errors_to=%s\n",
-    (addr->transport == NULL)? US"<unset>" : addr->transport->name,
+    addr->transport ? addr->transport->name : US"<unset>",
     addr->local_part, addr->domain, addr->prop.errors_address);
   debug_printf("  domain_data=%s localpart_data=%s\n", addr->prop.domain_data,
     addr->prop.localpart_data);
diff --git a/src/src/search.c b/src/src/search.c
index 92b95a9..df10f77 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -41,7 +41,7 @@ static int open_filecount = 0;


/* Allow us to reset store used for lookups and lookup caching */

-static void *search_reset_point = NULL;
+static rmark search_reset_point = NULL;



@@ -269,8 +269,7 @@ open_filecount = 0;
for (int i = 0; i < lookup_list_count; i++) if (lookup_list[i]->tidy)
(lookup_list[i]->tidy)();

-if (search_reset_point) store_reset(search_reset_point);
-search_reset_point = NULL;
+if (search_reset_point) search_reset_point = store_reset(search_reset_point);
store_pool = old_pool;
}

@@ -339,7 +338,7 @@ int old_pool = store_pool;
/* Change to the search store pool and remember our reset point */

store_pool = POOL_SEARCH;
-if (search_reset_point == NULL) search_reset_point = store_get(0);
+if (!search_reset_point) search_reset_point = store_mark();

DEBUG(D_lookup) debug_printf_indent("search_open: %s \"%s\"\n", lk->name,
filename ? filename : US"NULL");
@@ -418,8 +417,8 @@ count alone. */

 if (!t)
   {
-  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer));
-  t->data.ptr = c = store_get(sizeof(search_cache));
+  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), FALSE);
+  t->data.ptr = c = store_get(sizeof(search_cache), FALSE);
   c->item_cache = NULL;
   Ustrcpy(t->name, keybuffer);
   tree_insertnode(&search_tree, t);
@@ -537,7 +536,7 @@ else
       }
     else
       {
-      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len);
+      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring));
       e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
       e->ptr = data;
       t = (tree_node *)(e+1);
@@ -685,7 +684,8 @@ else if (partial >= 0)


   if (affixlen == 0) keystring2 = keystring; else
     {
-    keystring2 = store_get(len + affixlen + 1);
+    keystring2 = store_get(len + affixlen + 1,
+            is_tainted(keystring) || is_tainted(affix));
     Ustrncpy(keystring2, affix, affixlen);
     Ustrcpy(keystring2 + affixlen, keystring);
     DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring2);
diff --git a/src/src/setenv.c b/src/src/setenv.c
index 9a05b64..0274830 100644
--- a/src/src/setenv.c
+++ b/src/src/setenv.c
@@ -17,7 +17,7 @@ setenv(const char * name, const char * val, int overwrite)
 uschar * s;
 if (Ustrchr(name, '=')) return -1;
 if (overwrite || !getenv(name))
-  putenv(CS string_copy_malloc(string_sprintf("%s=%s", name, val)));
+  putenv(CS string_copy_perm(string_sprintf("%s=%s", name, val)), FALSE);
 return 0;
 }


diff --git a/src/src/sieve.c b/src/src/sieve.c
index 2317f2e..5e8d1e6 100644
--- a/src/src/sieve.c
+++ b/src/src/sieve.c
@@ -245,7 +245,7 @@ for (int pass = 0; pass <= 1; pass++)
     dst->length=0;
   else
     {
-    dst->character=store_get(dst->length+1); /* plus one for \0 */
+    dst->character = store_get(dst->length+1, is_tainted(src->character)); /* plus one for \0 */
     new=dst->character;
     }
   for (const uschar * start = src->character, * end = start + src->length;
@@ -444,9 +444,9 @@ if (*uri && *uri!='?')
         filter->errmsg=US"Invalid URI encoding";
         return -1;
         }
-      new=store_get(sizeof(string_item));
-      new->text=store_get(to.length+1);
-      if (to.length) memcpy(new->text,to.character,to.length);
+      new=store_get(sizeof(string_item), FALSE);
+      new->text = store_get(to.length+1, is_tainted(to.character));
+      if (to.length) memcpy(new->text, to.character, to.length);
       new->text[to.length]='\0';
       new->next=*recipient;
       *recipient=new;
@@ -502,9 +502,9 @@ if (*uri=='?')
       }
     if (hname.length==2 && strcmpic(hname.character, US"to")==0)
       {
-      new=store_get(sizeof(string_item));
-      new->text=store_get(hvalue.length+1);
-      if (hvalue.length) memcpy(new->text,hvalue.character,hvalue.length);
+      new=store_get(sizeof(string_item), FALSE);
+      new->text = store_get(hvalue.length+1, is_tainted(hvalue.character));
+      if (hvalue.length) memcpy(new->text, hvalue.character, hvalue.length);
       new->text[hvalue.length]='\0';
       new->next=*recipient;
       *recipient=new;
@@ -1087,7 +1087,8 @@ uschar *errmsg;
 value->length=0;
 value->character=(uschar*)0;


-t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
+t = r = s = expand_string(string_sprintf("$rheader_%s",quote(header)));
+if (!t) return;
 while (*r==' ' || *r=='\t') ++r;
 while (*r)
   {
@@ -1728,7 +1729,7 @@ if (*filter->pc=='[') /* string list */
       struct String *new;


       dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
-      new = store_get(sizeof(struct String) * dataCapacity);
+      new = store_get(sizeof(struct String) * dataCapacity, FALSE);


       if (d) memcpy(new,d,sizeof(struct String)*dataLength);
       d = new;
@@ -1766,15 +1767,13 @@ if (*filter->pc=='[') /* string list */
   }
 else /* single string */
   {
-  if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
-    {
+  if (!(d=store_get(sizeof(struct String)*2, FALSE)))
     return -1;
-    }
+
   m=parse_string(filter,&d[0]);
   if (m==-1)
-    {
     return -1;
-    }
+
   else if (m==0)
     {
     filter->pc=orig;
@@ -2119,8 +2118,7 @@ if (parse_identifier(filter,CUS "address"))
     if (exec)
       {
       /* We are only interested in addresses below, so no MIME decoding */
-      header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
-      if (header_value == NULL)
+      if (!(header_value = expand_string(string_sprintf("$rheader_%s",quote(h)))))
         {
         filter->errmsg=CUS "header string expansion failed";
         return -1;
@@ -2227,8 +2225,8 @@ else if (parse_identifier(filter,CUS "exists"))
       {
       uschar *header_def;


-      header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
-      if (header_def == NULL)
+      header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
+      if (!header_def)
         {
         filter->errmsg=CUS "header string expansion failed";
         return -1;
@@ -2311,8 +2309,8 @@ else if (parse_identifier(filter,CUS "header"))
       uschar *header_def;


       expand_header(&header_value,h);
-      header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
-      if (header_value.character == NULL || header_def == NULL)
+      header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
+      if (!header_value.character || !header_def)
         {
         filter->errmsg=CUS "header string expansion failed";
         return -1;
@@ -2495,7 +2493,7 @@ else if (parse_identifier(filter,CUS "envelope"))
       }
     if (exec && envelopeExpr)
       {
-      if ((envelope=expand_string(US envelopeExpr)) == NULL)
+      if (!(envelope=expand_string(US envelopeExpr)))
         {
         filter->errmsg=CUS "header string expansion failed";
         return -1;
@@ -2992,7 +2990,13 @@ while (*filter->pc)
     subject.character=(uschar*)0;
     body.length=-1;
     body.character=(uschar*)0;
-    envelope_from=(sender_address && sender_address[0]) ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
+    envelope_from = sender_address && sender_address[0]
+     ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
+    if (!envelope_from)
+      {
+      filter->errmsg=CUS "expansion failure for envelope from";
+      return -1;
+      }
     for (;;)
       {
       if (parse_white(filter)==-1) return -1;
@@ -3048,8 +3052,8 @@ while (*filter->pc)
       if (message.length==-1) message=subject;
       if (message.length==-1) expand_header(&message,&str_subject);
       expand_header(&auto_submitted_value,&str_auto_submitted);
-      auto_submitted_def=expand_string(string_sprintf("${if def:header_auto-submitted {true}{false}}"));
-      if (auto_submitted_value.character == NULL || auto_submitted_def == NULL)
+      auto_submitted_def=expand_string(US"${if def:header_auto-submitted {true}{false}}");
+      if (!auto_submitted_value.character || !auto_submitted_def)
         {
         filter->errmsg=CUS "header string expansion failed";
         return -1;
@@ -3069,8 +3073,7 @@ while (*filter->pc)
         if (!already)
           /* New notification, process it */
           {
-          struct Notification *sent;
-          sent=store_get(sizeof(struct Notification));
+          struct Notification * sent = store_get(sizeof(struct Notification), FALSE);
           sent->method=method;
           sent->importance=importance;
           sent->message=message;
@@ -3088,7 +3091,8 @@ while (*filter->pc)
               int buffer_capacity;


               f = fdopen(fd, "wb");
-              fprintf(f,"From: %s\n",from.length==-1 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : from.character);
+              fprintf(f,"From: %s\n", from.length == -1
+        ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : from.character);
               for (string_item * p = recipient; p; p=p->next)
                fprintf(f,"To: %s\n",p->text);
               fprintf(f,"Auto-Submitted: auto-notified; %s\n",filter->enotify_mailto_owner);
@@ -3100,7 +3104,7 @@ while (*filter->pc)
                 }
               /* Allocation is larger than necessary, but enough even for split MIME words */
               buffer_capacity=32+4*message.length;
-              buffer=store_get(buffer_capacity);
+              buffer=store_get(buffer_capacity, TRUE);
               if (message.length!=-1) fprintf(f,"Subject: %s\n",parse_quote_2047(message.character, message.length, US"utf-8", buffer, buffer_capacity, TRUE));
               fprintf(f,"\n");
               if (body.length>0) fprintf(f,"%s\n",body.character);
@@ -3220,9 +3224,9 @@ while (*filter->pc)
           }
         for (struct String * a = addresses; a->length != -1; ++a)
           {
-          string_item * new = store_get(sizeof(string_item));
+          string_item * new = store_get(sizeof(string_item), FALSE);


-          new->text=store_get(a->length+1);
+          new->text = store_get(a->length+1, is_tainted(a->character));
           if (a->length) memcpy(new->text,a->character,a->length);
           new->text[a->length]='\0';
           new->next=aliases;
@@ -3313,8 +3317,8 @@ while (*filter->pc)
             {
             uschar *subject_def;


-            subject_def=expand_string(US"${if def:header_subject {true}{false}}");
-            if (Ustrcmp(subject_def,"true")==0)
+            subject_def = expand_string(US"${if def:header_subject {true}{false}}");
+            if (subject_def && Ustrcmp(subject_def,"true")==0)
               {
           gstring * g = string_catn(NULL, US"Auto: ", 6);


@@ -3337,7 +3341,7 @@ while (*filter->pc)
           addr->prop.ignore_error = TRUE;
           addr->next = *generated;
           *generated = addr;
-          addr->reply = store_get(sizeof(reply_item));
+          addr->reply = store_get(sizeof(reply_item), FALSE);
           memset(addr->reply,0,sizeof(reply_item)); /* XXX */
           addr->reply->to = string_copy(sender_address);
           if (from.length==-1)
@@ -3346,7 +3350,7 @@ while (*filter->pc)
             addr->reply->from = from.character;
           /* Allocation is larger than necessary, but enough even for split MIME words */
           buffer_capacity=32+4*subject.length;
-          buffer=store_get(buffer_capacity);
+          buffer = store_get(buffer_capacity, is_tainted(subject.character));
       /* deconst cast safe as we pass in a non-const item */
           addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
           addr->reply->oncelog = string_from_gstring(once);
@@ -3582,14 +3586,13 @@ options = options; /* Keep picky compilers happy */
 error = error;


DEBUG(D_route) debug_printf("Sieve: start of processing\n");
-sieve.filter=filter;
+sieve.filter = filter;

-if (vacation_directory == NULL)
+if (!vacation_directory)
   sieve.vacation_directory = NULL;
 else
   {
-  sieve.vacation_directory=expand_string(vacation_directory);
-  if (sieve.vacation_directory == NULL)
+  if (!(sieve.vacation_directory = expand_string(vacation_directory)))
     {
     *error = string_sprintf("failed to expand \"%s\" "
       "(sieve_vacation_directory): %s", vacation_directory,
@@ -3598,12 +3601,11 @@ else
     }
   }


-if (enotify_mailto_owner == NULL)
+if (!enotify_mailto_owner)
   sieve.enotify_mailto_owner = NULL;
 else
   {
-  sieve.enotify_mailto_owner=expand_string(enotify_mailto_owner);
-  if (sieve.enotify_mailto_owner == NULL)
+  if (!(sieve.enotify_mailto_owner = expand_string(enotify_mailto_owner)))
     {
     *error = string_sprintf("failed to expand \"%s\" "
       "(sieve_enotify_mailto_owner): %s", enotify_mailto_owner,
@@ -3612,7 +3614,8 @@ else
     }
   }


-sieve.useraddress = useraddress == NULL ? CUS "$local_part_prefix$local_part$local_part_suffix" : useraddress;
+sieve.useraddress = useraddress
+ ? useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
sieve.subaddress = subaddress;

 #ifdef COMPILE_SYNTAX_CHECKER
@@ -3624,12 +3627,12 @@ if (parse_start(&sieve,1,generated)==1)
   if (sieve.keep)
     {
     add_addr(generated,US"inbox",1,0,0,0);
-    msg = string_sprintf("Implicit keep");
+    msg = US"Implicit keep";
     r = FF_DELIVERED;
     }
   else
     {
-    msg = string_sprintf("No implicit keep");
+    msg = US"No implicit keep";
     r = FF_DELIVERED;
     }
   }
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 1e478a6..24978c8 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -433,7 +433,7 @@ if (!sender_address                /* No transaction in progress */


 if (recipients_count > 0)
   {
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
+  raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
   for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = recipients_list[i].address;
   raw_recipients_count = recipients_count;
@@ -527,8 +527,8 @@ if (rc <= 0)
       smtp_data_sigint_exit();


     smtp_had_error = save_errno;
-    smtp_read_error = string_copy_malloc(
-      string_sprintf(" (error: %s)", strerror(save_errno)));
+    smtp_read_error = string_copy_perm(
+      string_sprintf(" (error: %s)", strerror(save_errno)), FALSE);
     }
   else
     smtp_had_eof = 1;
@@ -898,6 +898,7 @@ va_end(ap);
 /* This is split off so that verify.c:respond_printf() can, in effect, call
 smtp_printf(), bearing in mind that in C a vararg function can't directly
 call another vararg function, only a function which accepts a va_list. */
+/*XXX consider passing caller-info in, for string_vformat-onward */


void
smtp_vprintf(const char *format, BOOL more, va_list ap)
@@ -905,19 +906,20 @@ smtp_vprintf(const char *format, BOOL more, va_list ap)
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
BOOL yield;

-yield = !! string_vformat(&gs, FALSE, format, ap);
+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that we'll never expand it. */
+
+yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
string_from_gstring(&gs);

 DEBUG(D_receive)
   {
-  void *reset_point = store_get(0);
   uschar *msg_copy, *cr, *end;
   msg_copy = string_copy(gs.s);
   end = msg_copy + gs.ptr;
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
     memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", msg_copy);
-  store_reset(reset_point);
   }


if (!yield)
@@ -1906,11 +1908,7 @@ BOOL yield = fl.helo_accept_junk;

/* Discard any previous helo name */

-if (sender_helo_name)
- {
- store_free(sender_helo_name);
- sender_helo_name = NULL;
- }
+sender_helo_name = NULL;

/* Skip tests if junk is permitted. */

@@ -1949,7 +1947,7 @@ if (!yield)

/* Save argument if OK */

-if (yield) sender_helo_name = string_copy_malloc(start);
+if (yield) sender_helo_name = string_copy_perm(start, TRUE);
return yield;
}

@@ -2021,7 +2019,7 @@ Argument:   the stacking pool storage reset point
 Returns:    nothing
 */


-void
+void *
smtp_reset(void *reset_point)
{
recipients_list = NULL;
@@ -2131,6 +2129,7 @@ while (acl_warn_logged)
store_free(this);
}
store_reset(reset_point);
+return store_mark();
}


@@ -2158,7 +2157,7 @@ static int
smtp_setup_batch_msg(void)
{
int done = 0;
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();

/* Save the line count at the start of each transaction - single commands
like HELO and RSET count as whole transactions. */
@@ -2168,7 +2167,7 @@ bsmtp_transaction_linecount = receive_linecount;
if ((receive_feof)()) return 0; /* Treat EOF as QUIT */

 cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
-smtp_reset(reset_point);                /* Reset for start of message */
+reset_point = smtp_reset(reset_point);                /* Reset for start of message */


/* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
value. The values are 2 larger than the required yield of the function. */
@@ -2193,7 +2192,7 @@ while (done <= 0)

     case RSET_CMD:
       cancel_cutthrough_connection(TRUE, US"RSET received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       bsmtp_transaction_linecount = receive_linecount;
       break;


@@ -2217,7 +2216,7 @@ while (done <= 0)
       /* Reset to start of message */


       cancel_cutthrough_connection(TRUE, US"MAIL received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);


       /* Apply SMTP rewrite */


@@ -2482,11 +2481,9 @@ fl.smtputf8_advertised = FALSE;

acl_var_c = NULL;

-/* Allow for trailing 0 in the command and data buffers. */
+/* Allow for trailing 0 in the command and data buffers. Tainted. */

-if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "malloc() failed for SMTP command buffer");
+smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE);


 smtp_cmd_buffer[0] = 0;
 smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
@@ -2597,7 +2594,7 @@ if (!f.sender_host_unknown)
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
-    struct ip_options *ipopt = store_get(optlen);
+    struct ip_options *ipopt = store_get(optlen, FALSE);
     #elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
@@ -3506,7 +3503,7 @@ if (code && defaultrespond)
     va_list ap;


     va_start(ap, defaultrespond);
-    g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
+    g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap);
     va_end(ap);
     smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
     }
@@ -3719,7 +3716,7 @@ switch(rc)
   case OK:
   if (!au->set_id || set_id)    /* Complete success */
     {
-    if (set_id) authenticated_id = string_copy_malloc(set_id);
+    if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
     sender_host_authenticated = au->name;
     sender_host_auth_pubname  = au->public_name;
     authentication_failed = FALSE;
@@ -3740,7 +3737,7 @@ switch(rc)
   /* Fall through */


   case DEFER:
-  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
   *s = string_sprintf("435 Unable to authenticate at present%s",
     auth_defer_user_msg);
   *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
@@ -3760,13 +3757,13 @@ switch(rc)
   break;


case FAIL:
- if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+ if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
*s = US"535 Incorrect authentication data";
*ss = string_sprintf("535 Incorrect authentication data%s", set_id);
break;

   default:
-  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
   *s = US"435 Internal error";
   *ss = string_sprintf("435 Internal error%s: return %d from authentication "
     "check", set_id, rc);
@@ -3878,7 +3875,7 @@ BOOL toomany = FALSE;
 BOOL discarded = FALSE;
 BOOL last_was_rej_mail = FALSE;
 BOOL last_was_rcpt = FALSE;
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();


DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");

@@ -3888,7 +3885,7 @@ message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of
TLS between messages (an Exim client may do this if it has messages queued up
for the host). Note: we do NOT reset AUTH at this point. */

-smtp_reset(reset_point);
+reset_point = smtp_reset(reset_point);
message_ended = END_NOTSTARTED;

 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
@@ -4205,11 +4202,7 @@ while (done <= 0)
           &user_msg, &log_msg)) != OK)
       {
       done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
-      if (sender_helo_name)
-        {
-        store_free(sender_helo_name);
-        sender_helo_name = NULL;
-        }
+      sender_helo_name = NULL;
       host_build_sender_fullhost();  /* Rebuild */
       break;
       }
@@ -4239,7 +4232,9 @@ while (done <= 0)
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
     {
-    g = string_fmt_append(NULL, "%.3s %s Hello %s%s%s",
+    /* sender_host_name below will be tainted, so save on copy when we hit it */
+    g = string_get_tainted(24, TRUE);
+    g = string_fmt_append(g, "%.3s %s Hello %s%s%s",
       smtp_code,
       smtp_active_hostname,
       sender_ident ? sender_ident : US"",
@@ -4493,7 +4488,7 @@ while (done <= 0)
       + (tls_in.active.sock >= 0 ? pcrpted : 0)
       ];
       cancel_cutthrough_connection(TRUE, US"sent EHLO response");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       break;   /* HELO/EHLO */


@@ -4548,7 +4543,7 @@ while (done <= 0)
       obviously need to throw away any previous data. */


       cancel_cutthrough_connection(TRUE, US"MAIL received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       sender_data = recipient_data = NULL;


@@ -5424,7 +5419,7 @@ while (done <= 0)

       incomplete_transaction_log(US"STARTTLS");
       cancel_cutthrough_connection(TRUE, US"STARTTLS received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;


@@ -5472,7 +5467,6 @@ while (done <= 0)
     cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
     if (sender_helo_name)
       {
-      store_free(sender_helo_name);
       sender_helo_name = NULL;
       host_build_sender_fullhost();  /* Rebuild */
       set_process_info("handling incoming TLS connection from %s",
@@ -5563,7 +5557,7 @@ while (done <= 0)
     case RSET_CMD:
       smtp_rset_handler();
       cancel_cutthrough_connection(TRUE, US"RSET received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       break;


@@ -5585,17 +5579,17 @@ while (done <= 0)
     {
     uschar buffer[256];
     buffer[0] = 0;
-    Ustrcat(buffer, " AUTH");
+    Ustrcat(buffer, US" AUTH");
     #ifndef DISABLE_TLS
     if (tls_in.active.sock < 0 &&
         verify_check_host(&tls_advertise_hosts) != FAIL)
-      Ustrcat(buffer, " STARTTLS");
+      Ustrcat(buffer, US" STARTTLS");
     #endif
-    Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
-    Ustrcat(buffer, " NOOP QUIT RSET HELP");
-    if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
-    if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
-    if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
+    Ustrcat(buffer, US" HELO EHLO MAIL RCPT DATA BDAT");
+    Ustrcat(buffer, US" NOOP QUIT RSET HELP");
+    if (acl_smtp_etrn) Ustrcat(buffer, US" ETRN");
+    if (acl_smtp_expn) Ustrcat(buffer, US" EXPN");
+    if (acl_smtp_vrfy) Ustrcat(buffer, US" VRFY");
     smtp_printf("214%s\r\n", FALSE, buffer);
     }
       break;
@@ -5771,7 +5765,7 @@ while (done <= 0)
       }


     enq_end(etrn_serialize_key);
-    _exit(EXIT_SUCCESS);
+    exim_underbar_exit(EXIT_SUCCESS);
     }


       /* Back in the top level SMTP process. Check that we started a subprocess
@@ -5785,10 +5779,10 @@ while (done <= 0)
     if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
     }
       else
-    {
-    if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
-      else smtp_user_msg(US"250", user_msg);
-    }
+    if (!user_msg)
+      smtp_printf("250 OK\r\n", FALSE);
+    else
+      smtp_user_msg(US"250", user_msg);


       signal(SIGCHLD, oldsignal);
       break;
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 4f8a84e..ece79de 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -52,6 +52,17 @@ if (!(expint = expand_string(istring)))
   return FALSE;
   }


+if (is_tainted(expint))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "attempt to use tainted value '%s' from '%s' for interface",
+    expint, istring);
+  addr->transport_return = PANIC;
+  addr->message = string_sprintf("failed to expand \"interface\" "
+      "option for %s: configuration error", msg);
+  return FALSE;
+  }
+
 while (isspace(*expint)) expint++;
 if (*expint == 0) return TRUE;


@@ -521,8 +532,14 @@ if (format)
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
va_list ap;

+  /* Use taint-unchecked routines for writing into big_buffer, trusting that
+  we'll never expand the results.  Actually, the error-message use - leaving
+  the results in big_buffer for potential later use - is uncomfortably distant.
+  XXX Would be better to assume all smtp commands are short, use normal pool
+  alloc rather than big_buffer, and another global for the data-for-error. */
+
   va_start(ap, format);
-  if (!string_vformat(&gs, FALSE, CS format, ap))
+  if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, CS format, ap))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
       "SMTP");
   va_end(ap);
@@ -538,7 +555,7 @@ if (format)
     if (!flush_buffer(outblock, SCMD_FLUSH)) return -1;
     }


-  Ustrncpy(CS outblock->ptr, gs.s, gs.ptr);
+  Ustrncpy(outblock->ptr, gs.s, gs.ptr);
   outblock->ptr += gs.ptr;
   outblock->cmd_count++;
   gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for error message */
diff --git a/src/src/spam.c b/src/src/spam.c
index 7e33485..4cc4a9a 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -293,7 +293,7 @@ start = time(NULL);
     uschar * s;


     DEBUG(D_acl) debug_printf_indent("spamd: addr entry '%s'\n", address);
-    sd = (spamd_address_container *)store_get(sizeof(spamd_address_container));
+    sd = store_get(sizeof(spamd_address_container), FALSE);


     for (sublist = address, args = 0, spamd_param_init(sd);
      (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0));
@@ -565,7 +565,7 @@ else
     }


   Ustrcpy(spam_action_buffer,
-    spamd_score >= spamd_threshold ? "reject" : "no action");
+    spamd_score >= spamd_threshold ? US"reject" : US"no action");
   }


/* Create report. Since this is a multiline string,
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index c5733f1..101a6c4 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -185,7 +185,7 @@ BOOL right = buffer[1] == 'Y';

 if (n < 5) return FALSE;    /* malformed line */
 buffer[n-1] = 0;            /* Remove \n */
-node = store_get(sizeof(tree_node) + n - 3);
+node = store_get(sizeof(tree_node) + n - 3, is_tainted(buffer));
 *connect = node;
 Ustrcpy(node->name, buffer + 3);
 node->data.ptr = NULL;
@@ -411,7 +411,7 @@ n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;


-sender_address = store_get(n-2);
+sender_address = store_get(n-2, TRUE);    /* tainted */
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;


@@ -440,12 +440,16 @@ by not re-scanning the first two characters.
To allow new versions of Exim that add additional flags to interwork with older
versions that do not understand them, just ignore any lines starting with "-"
that we don't recognize. Otherwise it wouldn't be possible to back off a new
-version that left new-style flags written on the spool. */
+version that left new-style flags written on the spool.
+
+If the line starts with "--" the content of the variable is tainted. */

-p = big_buffer + 2;
 for (;;)
   {
   int len;
+  BOOL tainted;
+  uschar * var;
+
   if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
   while (  (len = Ustrlen(big_buffer)) == big_buffer_size-1
@@ -454,7 +458,7 @@ for (;;)
     {    /* buffer not big enough for line; certs make this possible */
     uschar * buf;
     if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
-    buf = store_get_perm(big_buffer_size *= 2);
+    buf = store_get_perm(big_buffer_size *= 2, FALSE);
     memcpy(buf, big_buffer, --len);
     big_buffer = buf;
     if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL)
@@ -462,7 +466,11 @@ for (;;)
     }
   big_buffer[len-1] = 0;


-  switch(big_buffer[1])
+  tainted = big_buffer[1] == '-';
+  var =  big_buffer + (tainted ? 2 : 1);
+  p = var + 1;
+
+  switch(*var)
     {
     case 'a':


@@ -477,13 +485,13 @@ for (;;)
       uschar *name, *endptr;
       int count;
       tree_node *node;
-      endptr = Ustrchr(big_buffer + 6, ' ');
+      endptr = Ustrchr(var + 5, ' ');
       if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
-      name = string_sprintf("%c%.*s", big_buffer[4],
-        (int)(endptr - big_buffer - 6), big_buffer + 6);
+      name = string_sprintf("%c%.*s", var[3],
+        (int)(endptr - var - 5), var + 5);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
-      node->data.ptr = store_get(count + 1);
+      node->data.ptr = store_get(count + 1, tainted);
       if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
       ((uschar*)node->data.ptr)[count] = 0;
       }
@@ -494,11 +502,11 @@ for (;;)
       f.allow_unqualified_sender = TRUE;


     else if (Ustrncmp(p, "uth_id", 6) == 0)
-      authenticated_id = string_copy(big_buffer + 9);
+      authenticated_id = string_copy_taint(var + 8, tainted);
     else if (Ustrncmp(p, "uth_sender", 10) == 0)
-      authenticated_sender = string_copy(big_buffer + 13);
+      authenticated_sender = string_copy_taint(var + 12, tainted);
     else if (Ustrncmp(p, "ctive_hostname", 14) == 0)
-      smtp_active_hostname = string_copy(big_buffer + 17);
+      smtp_active_hostname = string_copy_taint(var + 16, tainted);


     /* For long-term backward compatibility, we recognize "-acl", which was
     used before the number of ACL variables changed from 10 to 20. This was
@@ -512,7 +520,7 @@ for (;;)
       unsigned index, count;
       uschar name[20];   /* Need plenty of space for %u format */
       tree_node * node;
-      if (  sscanf(CS big_buffer + 5, "%u %u", &index, &count) != 2
+      if (  sscanf(CS var + 4, "%u %u", &index, &count) != 2
      || index >= 20
      || count > 16384    /* arbitrary limit on variable size */
          )
@@ -522,7 +530,7 @@ for (;;)
       else
         (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10);
       node = acl_var_create(name);
-      node->data.ptr = store_get(count + 1);
+      node->data.ptr = store_get(count + 1, tainted);
       /* We sanity-checked the count, so disable the Coverity error */
       /* coverity[tainted_data] */
       if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
@@ -532,12 +540,12 @@ for (;;)


     case 'b':
     if (Ustrncmp(p, "ody_linecount", 13) == 0)
-      body_linecount = Uatoi(big_buffer + 15);
+      body_linecount = Uatoi(var + 14);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
-      body_zerocount = Uatoi(big_buffer + 15);
+      body_zerocount = Uatoi(var + 14);
 #ifdef EXPERIMENTAL_BRIGHTMAIL
     else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
-      bmi_verdicts = string_copy(big_buffer + 14);
+      bmi_verdicts = string_copy_taint(var + 13, tainted);
 #endif
     break;


@@ -546,16 +554,16 @@ for (;;)
       f.deliver_firsttime = TRUE;
     /* Check if the dsn flags have been set in the header file */
     else if (Ustrncmp(p, "sn_ret", 6) == 0)
-      dsn_ret= atoi(CS big_buffer + 8);
+      dsn_ret= atoi(CS var + 7);
     else if (Ustrncmp(p, "sn_envid", 8) == 0)
-      dsn_envid = string_copy(big_buffer + 11);
+      dsn_envid = string_copy_taint(var + 10, tainted);
     break;


     case 'f':
     if (Ustrncmp(p, "rozen", 5) == 0)
       {
       f.deliver_freeze = TRUE;
-      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
+      if (sscanf(CS var+6, TIME_T_FMT, &deliver_frozen_at) != 1)
     goto SPOOL_READ_ERROR;
       }
     break;
@@ -566,11 +574,11 @@ for (;;)
     else if (Ustrcmp(p, "ost_lookup_failed") == 0)
       host_lookup_failed = TRUE;
     else if (Ustrncmp(p, "ost_auth", 8) == 0)
-      sender_host_authenticated = string_copy(big_buffer + 11);
+      sender_host_authenticated = string_copy_taint(var + 10, tainted);
     else if (Ustrncmp(p, "ost_name", 8) == 0)
-      sender_host_name = string_copy(big_buffer + 11);
+      sender_host_name = string_copy_taint(var + 10, tainted);
     else if (Ustrncmp(p, "elo_name", 8) == 0)
-      sender_helo_name = string_copy(big_buffer + 11);
+      sender_helo_name = string_copy_taint(var + 10, tainted);


     /* We now record the port number after the address, separated by a
     dot. For compatibility during upgrading, do nothing if there
@@ -578,36 +586,37 @@ for (;;)


     else if (Ustrncmp(p, "ost_address", 11) == 0)
       {
-      sender_host_port = host_address_extract_port(big_buffer + 14);
-      sender_host_address = string_copy(big_buffer + 14);
+      sender_host_port = host_address_extract_port(var + 13);
+      sender_host_address = string_copy_taint(var + 13, tainted);
       }
     break;


     case 'i':
     if (Ustrncmp(p, "nterface_address", 16) == 0)
       {
-      interface_port = host_address_extract_port(big_buffer + 19);
-      interface_address = string_copy(big_buffer + 19);
+      interface_port = host_address_extract_port(var + 18);
+      interface_address = string_copy_taint(var + 18, tainted);
       }
     else if (Ustrncmp(p, "dent", 4) == 0)
-      sender_ident = string_copy(big_buffer + 7);
+      sender_ident = string_copy_taint(var + 6, tainted);
     break;


     case 'l':
     if (Ustrcmp(p, "ocal") == 0)
       f.sender_local = TRUE;
-    else if (Ustrcmp(big_buffer, "-localerror") == 0)
+    else if (Ustrcmp(var, "localerror") == 0)
       f.local_error_message = TRUE;
 #ifdef HAVE_LOCAL_SCAN
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
-      local_scan_data = string_copy(big_buffer + 12);
+      local_scan_data = string_copy_taint(var + 11, tainted);
 #endif
     break;


     case 'm':
-    if (Ustrcmp(p, "anual_thaw") == 0) f.deliver_manual_thaw = TRUE;
+    if (Ustrcmp(p, "anual_thaw") == 0)
+      f.deliver_manual_thaw = TRUE;
     else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
-      max_received_linelength = Uatoi(big_buffer + 24);
+      max_received_linelength = Uatoi(var + 23);
     break;


     case 'N':
@@ -616,11 +625,11 @@ for (;;)


     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
-      received_protocol = string_copy(big_buffer + 19);
+      received_protocol = string_copy_taint(var + 18, tainted);
     else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
       {
       unsigned usec;
-      if (sscanf(CS big_buffer + 21, "%u", &usec) == 1)
+      if (sscanf(CS var + 20, "%u", &usec) == 1)
     received_time.tv_usec = usec;
       }
     break;
@@ -630,11 +639,11 @@ for (;;)
       f.sender_set_untrusted = TRUE;
 #ifdef WITH_CONTENT_SCAN
     else if (Ustrncmp(p, "pam_bar ", 8) == 0)
-      spam_bar = string_copy(big_buffer + 10);
+      spam_bar = string_copy_taint(var + 9, tainted);
     else if (Ustrncmp(p, "pam_score ", 10) == 0)
-      spam_score = string_copy(big_buffer + 12);
+      spam_score = string_copy_taint(var + 11, tainted);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
-      spam_score_int = string_copy(big_buffer + 16);
+      spam_score_int = string_copy_taint(var + 15, tainted);
 #endif
 #ifndef COMPILE_UTILITY
     else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
@@ -654,22 +663,22 @@ for (;;)
       if (Ustrncmp(q, "certificate_verified", 20) == 0)
     tls_in.certificate_verified = TRUE;
       else if (Ustrncmp(q, "cipher", 6) == 0)
-    tls_in.cipher = string_copy(big_buffer + 12);
+    tls_in.cipher = string_copy_taint(var + 11, tainted);
 # ifndef COMPILE_UTILITY    /* tls support fns not built in */
       else if (Ustrncmp(q, "ourcert", 7) == 0)
-    (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+    (void) tls_import_cert(var + 12, &tls_in.ourcert);
       else if (Ustrncmp(q, "peercert", 8) == 0)
-    (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+    (void) tls_import_cert(var + 13, &tls_in.peercert);
 # endif
       else if (Ustrncmp(q, "peerdn", 6) == 0)
-    tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
+    tls_in.peerdn = string_unprinting(string_copy_taint(var + 11, tainted));
       else if (Ustrncmp(q, "sni", 3) == 0)
-    tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+    tls_in.sni = string_unprinting(string_copy_taint(var + 8, tainted));
       else if (Ustrncmp(q, "ocsp", 4) == 0)
-    tls_in.ocsp = big_buffer[10] - '0';
+    tls_in.ocsp = var[9] - '0';
 # ifdef EXPERIMENTAL_TLS_RESUME
       else if (Ustrncmp(q, "resumption", 10) == 0)
-    tls_in.resumption = big_buffer[16] - 'A';
+    tls_in.resumption = var[15] - 'A';
 # endif


       }
@@ -730,7 +739,7 @@ DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
 #endif  /* COMPILE_UTILITY */


recipients_list_max = rcount;
-recipients_list = store_get(rcount * sizeof(recipient_item));
+recipients_list = store_get(rcount * sizeof(recipient_item), FALSE);

 /* We sanitised the count and know we have enough memory, so disable
 the Coverity error on recipients_count */
@@ -796,6 +805,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   if (*p == ',')
     {
     int dummy;
+#if !defined (COMPILE_UTILITY)
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 3 spool file\n");
+#endif
     while (isdigit(*(--p)) || *p == ',');
     if (*p == ' ')
       {
@@ -808,6 +820,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)


   else if (*p == ' ')
     {
+#if !defined (COMPILE_UTILITY)
+    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - early Exim 4 spool file\n");
+#endif
     *p++ = 0;
     (void)sscanf(CS p, "%d", &pno);
     }
@@ -833,7 +848,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       if (len > 0)
         {
         p -= len;
-        errors_to = string_copy(p);
+        errors_to = string_copy_taint(p, TRUE);
         }
       }


@@ -847,7 +862,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       if (len > 0)
         {
         p -= len;
-        orcpt = string_copy(p);
+        orcpt = string_copy_taint(p, TRUE);
         }
       }


@@ -865,7 +880,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       big_buffer, errors_to);
 #endif


- recipients_list[recipients_count].address = string_copy(big_buffer);
+ recipients_list[recipients_count].address = string_copy_taint(big_buffer, TRUE);
recipients_list[recipients_count].pno = pno;
recipients_list[recipients_count].errors_to = errors_to;
recipients_list[recipients_count].orcpt = orcpt;
@@ -897,11 +912,11 @@ while ((n = fgetc(fp)) != EOF)

   if (read_headers)
     {
-    h = store_get(sizeof(header_line));
+    h = store_get(sizeof(header_line), FALSE);
     h->next = NULL;
     h->type = flag[0];
     h->slen = n;
-    h->text = store_get(n+1);
+    h->text = store_get(n+1, TRUE);    /* tainted */


     if (h->type == htype_received) received_count++;


diff --git a/src/src/spool_mbox.c b/src/src/spool_mbox.c
index 188f405..caf1712 100644
--- a/src/src/spool_mbox.c
+++ b/src/src/spool_mbox.c
@@ -38,13 +38,13 @@ uschar *mbox_path;
FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
struct stat statbuf;
int j;
-void *reset_point;
+rmark reset_point;

mbox_path = string_sprintf("%s/scan/%s/%s.eml",
spool_directory, message_id, message_id);
if (mbox_fname) *mbox_fname = mbox_path;

-reset_point = store_get(0);
+reset_point = store_mark();

/* Skip creation if already spooled out as mbox file */
if (!spool_mbox_ok)
@@ -210,12 +210,11 @@ malware_ok = 0;

if (spool_mbox_ok && !f.no_mbox_unspool)
{
- uschar *mbox_path;
uschar *file_path;
struct dirent *entry;
DIR *tempdir;
-
- mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
+ rmark reset_point = store_mark();
+ uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);

   if (!(tempdir = opendir(CS mbox_path)))
     {
@@ -240,7 +239,7 @@ if (spool_mbox_ok && !f.no_mbox_unspool)


/* remove directory */
rmdir(CS mbox_path);
- store_reset(mbox_path);
+ store_reset(reset_point);
}
spool_mbox_ok = 0;
}
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 6ceae38..0dfa4de 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -104,6 +104,13 @@ return fd;



+static void
+spool_var_write(FILE * fp, const uschar * name, const uschar * val)
+{
+if (is_tainted(val)) putc('-', fp);
+fprintf(fp, "-%s %s\n", name, val);
+}
+
 /*************************************************
 *          Write the header spool file           *
 *************************************************/
@@ -158,36 +165,40 @@ fprintf(fp, "-received_time_usec .%06d\n", (int)received_time.tv_usec);
 /* If there is information about a sending host, remember it. The HELO
 data can be set for local SMTP as well as remote. */


-if (sender_helo_name)
- fprintf(fp, "-helo_name %s\n", sender_helo_name);
+if (sender_helo_name) spool_var_write(fp, US"helo_name", sender_helo_name);

 if (sender_host_address)
   {
+  if (is_tainted(sender_host_address)) putc('-', fp);
   fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port);
   if (sender_host_name)
-    fprintf(fp, "-host_name %s\n", sender_host_name);
+    spool_var_write(fp, US"host_name", sender_host_name);
   if (sender_host_authenticated)
-    fprintf(fp, "-host_auth %s\n", sender_host_authenticated);
+    spool_var_write(fp, US"host_auth", sender_host_authenticated);
   }


/* Also about the interface a message came in on */

if (interface_address)
+ {
+ if (is_tainted(interface_address)) putc('-', fp);
fprintf(fp, "-interface_address %s.%d\n", interface_address, interface_port);
+ }

if (smtp_active_hostname != primary_hostname)
- fprintf(fp, "-active_hostname %s\n", smtp_active_hostname);
+ spool_var_write(fp, US"active_hostname", smtp_active_hostname);

/* Likewise for any ident information; for local messages this is
likely to be the same as originator_login, but will be different if
the originator was root, forcing a different ident. */

-if (sender_ident) fprintf(fp, "-ident %s\n", sender_ident);
+if (sender_ident)
+ spool_var_write(fp, US"ident", sender_ident);

/* Ditto for the received protocol */

if (received_protocol)
- fprintf(fp, "-received_protocol %s\n", received_protocol);
+ spool_var_write(fp, US"received_protocol", received_protocol);

/* Preserve any ACL variables that are set. */

@@ -205,9 +216,9 @@ fprintf(fp, "-max_received_linelength %d\n", max_received_linelength);
if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount);

if (authenticated_id)
- fprintf(fp, "-auth_id %s\n", authenticated_id);
+ spool_var_write(fp, US"auth_id", authenticated_id);
if (authenticated_sender)
- fprintf(fp, "-auth_sender %s\n", authenticated_sender);
+ spool_var_write(fp, US"auth_sender", authenticated_sender);

 if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n");
 if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n");
@@ -219,30 +230,30 @@ if (host_lookup_failed) fprintf(fp, "-host_lookup_failed\n");
 if (f.sender_local) fprintf(fp, "-local\n");
 if (f.local_error_message) fprintf(fp, "-localerror\n");
 #ifdef HAVE_LOCAL_SCAN
-if (local_scan_data) fprintf(fp, "-local_scan %s\n", local_scan_data);
+if (local_scan_data) spool_var_write(fp, US"local_scan", local_scan_data);
 #endif
 #ifdef WITH_CONTENT_SCAN
-if (spam_bar)       fprintf(fp,"-spam_bar %s\n",       spam_bar);
-if (spam_score)     fprintf(fp,"-spam_score %s\n",     spam_score);
-if (spam_score_int) fprintf(fp,"-spam_score_int %s\n", spam_score_int);
+if (spam_bar)       spool_var_write(fp, US"spam_bar",       spam_bar);
+if (spam_score)     spool_var_write(fp, US"spam_score",     spam_score);
+if (spam_score_int) spool_var_write(fp, US"spam_score_int", spam_score_int);
 #endif
 if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n");
 if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n");


#ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_verdicts) fprintf(fp, "-bmi_verdicts %s\n", bmi_verdicts);
+if (bmi_verdicts) spool_var_write(fp, US"bmi_verdicts", bmi_verdicts);
#endif

 #ifndef DISABLE_TLS
 if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n");
-if (tls_in.cipher)       fprintf(fp, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.cipher) spool_var_write(fp, US"tls_cipher", tls_in.cipher);
 if (tls_in.peercert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
-  fprintf(fp, "-tls_peercert %s\n", CS big_buffer);
+  fprintf(fp, "--tls_peercert %s\n", CS big_buffer);
   }
-if (tls_in.peerdn)       fprintf(fp, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
-if (tls_in.sni)         fprintf(fp, "-tls_sni %s\n",    string_printing(tls_in.sni));
+if (tls_in.peerdn)       spool_var_write(fp, US"tls_peerdn", string_printing(tls_in.peerdn));
+if (tls_in.sni)         spool_var_write(fp, US"tls_sni",    string_printing(tls_in.sni));
 if (tls_in.ourcert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
diff --git a/src/src/store.c b/src/src/store.c
index 94becbb..7871d0c 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -3,6 +3,7 @@
 *************************************************/


/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim maintainers 2019 */
/* See the file NOTICE for conditions of use and distribution. */

/* Exim gets and frees all its store through these functions. In the original
@@ -35,6 +36,13 @@ The following different types of store are recognized:
. There is a separate pool (POOL_SEARCH) that is used only for lookup storage.
This means it can be freed when search_tidyup() is called to close down all
the lookup caching.
+
+. Orthogonal to the three pool types, there are two classes of memory: untainted
+ and tainted. The latter is used for values derived from untrusted input, and
+ the string-expansion mechanism refuses to operate on such values (obviously,
+ it can expand an untainted value to return a tainted result). The classes
+ are implemented by duplicating the three pool types. Pool resets are requested
+ against the nontainted sibling and apply to both siblings.
*/


@@ -42,6 +50,7 @@ The following different types of store are recognized:
/* keep config.h before memcheck.h, for NVALGRIND */
#include "config.h"

+#include <sys/mman.h>
#include "memcheck.h"


@@ -53,13 +62,7 @@ is a constant, the compiler should optimize it to a simple constant wherever it
appears (I checked that gcc does do this). */

#define alignment \
- ((sizeof(void *) > sizeof(double))? sizeof(void *) : sizeof(double))
-
-/* Size of block to get from malloc to carve up into smaller ones. This
-must be a multiple of the alignment. We assume that 8192 is going to be
-suitably aligned. */
-
-#define STORE_BLOCK_SIZE 8192
+ (sizeof(void *) > sizeof(double) ? sizeof(void *) : sizeof(double))

/* store_reset() will not free the following block if the last used block has
less than this much left in it. */
@@ -80,32 +83,103 @@ length. */
#define ALIGNED_SIZEOF_STOREBLOCK \
(((sizeof(storeblock) + alignment - 1) / alignment) * alignment)

+/* Size of block to get from malloc to carve up into smaller ones. This
+must be a multiple of the alignment. We assume that 8192 is going to be
+suitably aligned. */
+
+#define STORE_BLOCK_SIZE (8192 - ALIGNED_SIZEOF_STOREBLOCK)
+
/* Variables holding data for the local pools of store. The current pool number
is held in store_pool, which is global so that it can be changed from outside.
Setting the initial length values to -1 forces a malloc for the first call,
even if the length is zero (which is used for getting a point to reset to). */

-int store_pool = POOL_PERM;
+int store_pool = POOL_MAIN;

-static storeblock *chainbase[3] = { NULL, NULL, NULL };
-static storeblock *current_block[3] = { NULL, NULL, NULL };
-static void *next_yield[3] = { NULL, NULL, NULL };
-static int yield_length[3] = { -1, -1, -1 };
+#define NPOOLS 6
+static storeblock *chainbase[NPOOLS];
+static storeblock *current_block[NPOOLS];
+static void *next_yield[NPOOLS];
+static int yield_length[NPOOLS] = { -1, -1, -1, -1, -1, -1 };
+
+/* The limits of the tainted pools. Tracking these on new allocations enables
+a fast is_tainted implementation. We assume the kernel only allocates mmaps using
+one side or the other of data+heap, not both. */
+
+static void * tainted_base = (void *)-1;
+static void * tainted_top = (void *)0;

/* pool_malloc holds the amount of memory used by the store pools; this goes up
and down as store is reset or released. nonpool_malloc is the total got by
malloc from other calls; this doesn't go down because it is just freed by
pointer. */

-static int pool_malloc = 0;
-static int nonpool_malloc = 0;
+static int pool_malloc;
+static int nonpool_malloc;

/* This variable is set by store_get() to its yield, and by store_reset() to
NULL. This enables string_cat() to optimize its store handling for very long
strings. That's why the variable is global. */

-void *store_last_get[3] = { NULL, NULL, NULL };
+void *store_last_get[NPOOLS];
+
+/* These are purely for stats-gathering */
+
+static int nbytes[NPOOLS];    /* current bytes allocated */
+static int maxbytes[NPOOLS];    /* max number reached */
+static int nblocks[NPOOLS];    /* current number of blocks allocated */
+static int maxblocks[NPOOLS];
+static int n_nonpool_blocks;    /* current number of direct store_malloc() blocks */
+static int max_nonpool_blocks;
+static int max_pool_malloc;    /* max value for pool_malloc */
+static int max_nonpool_malloc;    /* max value for nonpool_malloc */
+
+
+static const uschar * pooluse[NPOOLS] = {
+[POOL_MAIN] =        US"main",
+[POOL_PERM] =        US"perm",
+[POOL_SEARCH] =        US"search",
+[POOL_TAINT_MAIN] =    US"main",
+[POOL_TAINT_PERM] =    US"perm",
+[POOL_TAINT_SEARCH] =    US"search",
+};
+static const uschar * poolclass[NPOOLS] = {
+[POOL_MAIN] =        US"untainted",
+[POOL_PERM] =        US"untainted",
+[POOL_SEARCH] =        US"untainted",
+[POOL_TAINT_MAIN] =    US"tainted",
+[POOL_TAINT_PERM] =    US"tainted",
+[POOL_TAINT_SEARCH] =    US"tainted",
+};
+
+
+static void * store_mmap(int, const char *, int);
+static void * internal_store_malloc(int, const char *, int);
+static void   internal_store_free(void *, const char *, int linenumber);
+
+/******************************************************************************/
+
+/* Predicate: if an address is in a tainted pool.
+By extension, a variable pointing to this address is tainted.
+*/
+
+BOOL
+is_tainted(const void * p)
+{
+BOOL rc = p >= tainted_base && p < tainted_top;


+#ifndef COMPILE_UTILITY
+DEBUG(D_memory) if (rc) debug_printf_indent("is_tainted: YES\n");
+#endif
+return rc;
+}
+
+void
+die_tainted(const uschar * msg, const uschar * func, int line)
+{
+log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n",
+    msg, func, line);
+}



/*************************************************
@@ -119,15 +193,17 @@ store_last_was_get.

 Arguments:
   size        amount wanted
-  filename    source file from which called
-  linenumber  line number in source file.
+  func        function from which called
+  linenumber  line number in source file


 Returns:      pointer to store (panic on malloc failure)
 */


void *
-store_get_3(int size, const char *filename, int linenumber)
+store_get_3(int size, BOOL tainted, const char *func, int linenumber)
{
+int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
+
/* Round up the size to a multiple of the alignment. Although this looks a
messy statement, because "alignment" is a constant expression, the compiler can
do a reasonable job of optimizing, especially if the value of "alignment" is a
@@ -140,21 +216,31 @@ if (size % alignment != 0) size += alignment - (size % alignment);
size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since
these functions are mostly called for small amounts of store. */

-if (size > yield_length[store_pool])
+if (size > yield_length[pool])
{
- int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
+ int length = size <= STORE_BLOCK_SIZE ? STORE_BLOCK_SIZE : size;
int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
- storeblock * newblock = NULL;
+ storeblock * newblock;

/* Sometimes store_reset() may leave a block for us; check if we can use it */

-  if (  (newblock = current_block[store_pool])
+  if (  (newblock = current_block[pool])
      && (newblock = newblock->next)
      && newblock->length < length
      )
     {
     /* Give up on this block, because it's too small */
-    store_free(newblock);
+    nblocks[pool]--;
+    if (pool < POOL_TAINT_BASE)
+      internal_store_free(newblock, func, linenumber);
+    else
+      {
+#ifndef COMPILE_UTILITY
+      DEBUG(D_memory)
+    debug_printf("---Unmap %6p %-20s %4d\n", newblock, func, linenumber);
+#endif
+      munmap(newblock, newblock->length + ALIGNED_SIZEOF_STOREBLOCK);
+      }
     newblock = NULL;
     }


@@ -162,53 +248,56 @@ if (size > yield_length[store_pool])

   if (!newblock)
     {
-    pool_malloc += mlength;           /* Used in pools */
-    nonpool_malloc -= mlength;        /* Exclude from overall total */
-    newblock = store_malloc(mlength);
+    if ((nbytes[pool] += mlength) > maxbytes[pool])
+      maxbytes[pool] = nbytes[pool];
+    if ((pool_malloc += mlength) > max_pool_malloc)    /* Used in pools */
+      max_pool_malloc = pool_malloc;
+    nonpool_malloc -= mlength;            /* Exclude from overall total */
+    if (++nblocks[pool] > maxblocks[pool])
+      maxblocks[pool] = nblocks[pool];
+
+    newblock = tainted
+      ? store_mmap(mlength, func, linenumber)
+      : internal_store_malloc(mlength, func, linenumber);
     newblock->next = NULL;
     newblock->length = length;
-    if (!chainbase[store_pool])
-      chainbase[store_pool] = newblock;
+
+    if (!chainbase[pool])
+      chainbase[pool] = newblock;
     else
-      current_block[store_pool]->next = newblock;
+      current_block[pool]->next = newblock;
     }


-  current_block[store_pool] = newblock;
-  yield_length[store_pool] = newblock->length;
-  next_yield[store_pool] =
-    (void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
-  (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
+  current_block[pool] = newblock;
+  yield_length[pool] = newblock->length;
+  next_yield[pool] =
+    (void *)(CS current_block[pool] + ALIGNED_SIZEOF_STOREBLOCK);
+  (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[pool], yield_length[pool]);
   }


/* There's (now) enough room in the current block; the yield is the next
pointer. */

-store_last_get[store_pool] = next_yield[store_pool];
+store_last_get[pool] = next_yield[pool];

/* Cut out the debugging stuff for utilities, but stop picky compilers from
giving warnings. */

 #ifdef COMPILE_UTILITY
-filename = filename;
+func = func;
 linenumber = linenumber;
 #else
 DEBUG(D_memory)
-  {
-  if (f.running_in_test_harness)
-    debug_printf("---%d Get %5d\n", store_pool, size);
-  else
-    debug_printf("---%d Get %6p %5d %-14s %4d\n", store_pool,
-      store_last_get[store_pool], size, filename, linenumber);
-  }
+  debug_printf("---%d Get %6p %5d %-14s %4d\n", pool,
+    store_last_get[pool], size, func, linenumber);
 #endif  /* COMPILE_UTILITY */


-(void) VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[store_pool], size);
+(void) VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[pool], size);
/* Update next pointer and number of bytes left in the current block. */

-next_yield[store_pool] = (void *)(CS next_yield[store_pool] + size);
-yield_length[store_pool] -= size;
-
-return store_last_get[store_pool];
+next_yield[pool] = (void *)(CS next_yield[pool] + size);
+yield_length[pool] -= size;
+return store_last_get[pool];
}


@@ -222,19 +311,19 @@ be obtained.

 Arguments:
   size        amount wanted
-  filename    source file from which called
-  linenumber  line number in source file.
+  func        function from which called
+  linenumber  line number in source file


 Returns:      pointer to store (panic on malloc failure)
 */


void *
-store_get_perm_3(int size, const char *filename, int linenumber)
+store_get_perm_3(int size, BOOL tainted, const char *func, int linenumber)
{
void *yield;
int old_pool = store_pool;
store_pool = POOL_PERM;
-yield = store_get_3(size, filename, linenumber);
+yield = store_get_3(size, tainted, func, linenumber);
store_pool = old_pool;
return yield;
}
@@ -247,15 +336,16 @@ return yield;

/* While reading strings of unknown length, it is often the case that the
string is being read into the block at the top of the stack. If it needs to be
-extended, it is more efficient just to extend the top block rather than
+extended, it is more efficient just to extend within the top block rather than
allocate a new block and then have to copy the data. This function is provided
for the use of string_cat(), but of course can be used elsewhere too.
+The block itself is not expanded; only the top allocation from it.

 Arguments:
   ptr        pointer to store block
   oldsize    current size of the block, as requested by user
   newsize    new size required
-  filename   source file from which called
+  func       function from which called
   linenumber line number in source file


 Returns:     TRUE if the block is at the top of the stack and has been
@@ -264,39 +354,41 @@ Returns:     TRUE if the block is at the top of the stack and has been
 */


BOOL
-store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
- int linenumber)
+store_extend_3(void *ptr, BOOL tainted, int oldsize, int newsize,
+ const char *func, int linenumber)
{
+int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
int inc = newsize - oldsize;
int rounded_oldsize = oldsize;

+/* Check that the block being extended was already of the required taint status;
+refuse to extend if not. */
+
+if (is_tainted(ptr) != tainted)
+ return FALSE;
+
if (rounded_oldsize % alignment != 0)
rounded_oldsize += alignment - (rounded_oldsize % alignment);

-if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
-    inc > yield_length[store_pool] + rounded_oldsize - oldsize)
+if (CS ptr + rounded_oldsize != CS (next_yield[pool]) ||
+    inc > yield_length[pool] + rounded_oldsize - oldsize)
   return FALSE;


/* Cut out the debugging stuff for utilities, but stop picky compilers from
giving warnings. */

 #ifdef COMPILE_UTILITY
-filename = filename;
+func = func;
 linenumber = linenumber;
 #else
 DEBUG(D_memory)
-  {
-  if (f.running_in_test_harness)
-    debug_printf("---%d Ext %5d\n", store_pool, newsize);
-  else
-    debug_printf("---%d Ext %6p %5d %-14s %4d\n", store_pool, ptr, newsize,
-      filename, linenumber);
-  }
+  debug_printf("---%d Ext %6p %5d %-14s %4d\n", pool, ptr, newsize,
+    func, linenumber);
 #endif  /* COMPILE_UTILITY */


if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
-next_yield[store_pool] = CS ptr + newsize;
-yield_length[store_pool] -= newsize - rounded_oldsize;
+next_yield[pool] = CS ptr + newsize;
+yield_length[pool] -= newsize - rounded_oldsize;
(void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
return TRUE;
}
@@ -309,44 +401,46 @@ return TRUE;
*************************************************/

/* This function resets the next pointer, freeing any subsequent whole blocks
-that are now unused. Normally it is given a pointer that was the yield of a
-call to store_get, and is therefore aligned, but it may be given an offset
-after such a pointer in order to release the end of a block and anything that
-follows.
+that are now unused. Call with a cookie obtained from store_mark() only; do
+not call with a pointer returned by store_get(). Both the untainted and tainted
+pools corresposding to store_pool are reset.

 Arguments:
-  ptr         place to back up to
-  filename    source file from which called
+  r           place to back up to
+  func        function from which called
   linenumber  line number in source file


 Returns:      nothing
 */


-void
-store_reset_3(void *ptr, const char *filename, int linenumber)
+static void
+internal_store_reset(void * ptr, int pool, const char *func, int linenumber)
{
storeblock * bb;
-storeblock * b = current_block[store_pool];
+storeblock * b = current_block[pool];
char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
-int newlength;
+int newlength, count;
+#ifndef COMPILE_UTILITY
+int oldmalloc = pool_malloc;
+#endif

/* Last store operation was not a get */

-store_last_get[store_pool] = NULL;
+store_last_get[pool] = NULL;

/* See if the place is in the current block - as it often will be. Otherwise,
search for the block in which it lies. */

 if (CS ptr < bc || CS ptr > bc + b->length)
   {
-  for (b = chainbase[store_pool]; b; b = b->next)
+  for (b = chainbase[pool]; b; b = b->next)
     {
     bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
     if (CS ptr >= bc && CS ptr <= bc + b->length) break;
     }
   if (!b)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal error: store_reset(%p) "
-      "failed: pool=%d %-14s %4d", ptr, store_pool, filename, linenumber);
+      "failed: pool=%d %-14s %4d", ptr, pool, func, linenumber);
   }


 /* Back up, rounding to the alignment if necessary. When testing, flatten
@@ -356,7 +450,7 @@ newlength = bc + b->length - CS ptr;
 #ifndef COMPILE_UTILITY
 if (debug_store)
   {
-  assert_no_variables(ptr, newlength, filename, linenumber);
+  assert_no_variables(ptr, newlength, func, linenumber);
   if (f.running_in_test_harness)
     {
     (void) VALGRIND_MAKE_MEM_DEFINED(ptr, newlength);
@@ -365,23 +459,25 @@ if (debug_store)
   }
 #endif
 (void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
-yield_length[store_pool] = newlength - (newlength % alignment);
-next_yield[store_pool] = CS ptr + (newlength % alignment);
-current_block[store_pool] = b;
-
-/* Free any subsequent block. Do NOT free the first successor, if our
-current block has less than 256 bytes left. This should prevent us from
-flapping memory. However, keep this block only when it has the default size. */
-
-if (yield_length[store_pool] < STOREPOOL_MIN_SIZE &&
-    b->next &&
-    b->next->length == STORE_BLOCK_SIZE)
+next_yield[pool] = CS ptr + (newlength % alignment);
+count = yield_length[pool];
+count = (yield_length[pool] = newlength - (newlength % alignment)) - count;
+current_block[pool] = b;
+
+/* Free any subsequent block. Do NOT free the first
+successor, if our current block has less than 256 bytes left. This should
+prevent us from flapping memory. However, keep this block only when it has
+the default size. */
+
+if (  yield_length[pool] < STOREPOOL_MIN_SIZE
+   && b->next
+   && b->next->length == STORE_BLOCK_SIZE)
   {
   b = b->next;
 #ifndef COMPILE_UTILITY
   if (debug_store)
     assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
-            filename, linenumber);
+            func, linenumber);
 #endif
   (void) VALGRIND_MAKE_MEM_NOACCESS(CS b + ALIGNED_SIZEOF_STOREBLOCK,
         b->length - ALIGNED_SIZEOF_STOREBLOCK);
@@ -392,36 +488,156 @@ b->next = NULL;


 while ((b = bb))
   {
+  int siz = b->length + ALIGNED_SIZEOF_STOREBLOCK;
 #ifndef COMPILE_UTILITY
   if (debug_store)
     assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
-            filename, linenumber);
+            func, linenumber);
 #endif
   bb = bb->next;
-  pool_malloc -= b->length + ALIGNED_SIZEOF_STOREBLOCK;
-  store_free_3(b, filename, linenumber);
+  nbytes[pool] -= siz;
+  pool_malloc -= siz;
+  nblocks[pool]--;
+  if (pool < POOL_TAINT_BASE)
+    internal_store_free(b, func, linenumber);
+  else
+    {
+#ifndef COMPILE_UTILITY
+    DEBUG(D_memory)
+      debug_printf("---Unmap %6p %-20s %4d\n", b, func, linenumber);
+#endif
+    munmap(b, b->length + ALIGNED_SIZEOF_STOREBLOCK);
+    }
   }


/* Cut out the debugging stuff for utilities, but stop picky compilers from
giving warnings. */

 #ifdef COMPILE_UTILITY
-filename = filename;
+func = func;
 linenumber = linenumber;
 #else
 DEBUG(D_memory)
+  debug_printf("---%d Rst %6p %5d %-14s %4d %d\n", pool, ptr,
+    count + oldmalloc - pool_malloc,
+    func, linenumber, pool_malloc);
+#endif  /* COMPILE_UTILITY */
+}
+
+
+rmark
+store_reset_3(rmark r, int pool, const char *func, int linenumber)
+{
+void ** ptr = r;
+
+if (pool >= POOL_TAINT_BASE)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "store_reset called for pool %d: %s %d\n", pool, func, linenumber);
+if (!r)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "store_reset called with bad mark: %s %d\n", func, linenumber);
+
+internal_store_reset(*ptr, pool + POOL_TAINT_BASE, func, linenumber);
+internal_store_reset(ptr,  pool,           func, linenumber);
+return NULL;
+}
+
+
+
+/* Free tail-end unused allocation.  This lets us allocate a big chunk
+early, for cases when we only discover later how much was really needed.
+
+Can be called with a value from store_get(), or an offset after such.  Only
+the tainted or untainted pool that serviced the store_get() will be affected.
+
+This is mostly a cut-down version of internal_store_reset().
+XXX needs rationalising
+*/
+
+void
+store_release_above_3(void *ptr, const char *func, int linenumber)
+{
+/* Search all pools' "current" blocks.  If it isn't one of those,
+ignore it (it usually will be). */
+
+for (int pool = 0; pool < nelem(current_block); pool++)
   {
-  if (f.running_in_test_harness)
-    debug_printf("---%d Rst    ** %d\n", store_pool, pool_malloc);
-  else
-    debug_printf("---%d Rst %6p    ** %-14s %4d %d\n", store_pool, ptr,
-      filename, linenumber, pool_malloc);
+  storeblock * b = current_block[pool];
+  char * bc;
+  int count, newlength;
+
+  if (!b)
+    continue;
+
+  bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
+  if (CS ptr < bc || CS ptr > bc + b->length)
+    continue;
+
+  /* Last store operation was not a get */
+
+  store_last_get[pool] = NULL;
+
+  /* Back up, rounding to the alignment if necessary. When testing, flatten
+  the released memory. */
+
+  newlength = bc + b->length - CS ptr;
+#ifndef COMPILE_UTILITY
+  if (debug_store)
+    {
+    assert_no_variables(ptr, newlength, func, linenumber);
+    if (f.running_in_test_harness)
+      {
+      (void) VALGRIND_MAKE_MEM_DEFINED(ptr, newlength);
+      memset(ptr, 0xF0, newlength);
+      }
+    }
+#endif
+  (void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
+  next_yield[pool] = CS ptr + (newlength % alignment);
+  count = yield_length[pool];
+  count = (yield_length[pool] = newlength - (newlength % alignment)) - count;
+
+  /* Cut out the debugging stuff for utilities, but stop picky compilers from
+  giving warnings. */
+
+#ifdef COMPILE_UTILITY
+  func = func;
+  linenumber = linenumber;
+#else
+  DEBUG(D_memory)
+    debug_printf("---%d Rel %6p %5d %-14s %4d %d\n", pool, ptr, count,
+      func, linenumber, pool_malloc);
+#endif
+  return;
   }
-#endif  /* COMPILE_UTILITY */
+#ifndef COMPILE_UTILITY
+DEBUG(D_memory)
+  debug_printf("non-last memory release try: %s %d\n", func, linenumber);
+#endif
 }




+rmark
+store_mark_3(const char *func, int linenumber)
+{
+void ** p;
+
+if (store_pool >= POOL_TAINT_BASE)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "store_mark called for pool %d: %s %d\n", store_pool, func, linenumber);
+
+/* Stash a mark for the tainted-twin release, in the untainted twin. Return
+a cookie (actually the address in the untainted pool) to the caller.
+Reset uses the cookie to recover the t-mark, winds back the tainted pool with it
+and winds back the untainted pool with the cookie. */
+
+p = store_get_3(sizeof(void *), FALSE, func, linenumber);
+*p = store_get_3(0, TRUE, func, linenumber);
+return p;
+}
+
+



/************************************************
@@ -433,38 +649,38 @@ block, and if so, releases that block.

 Arguments:
   block       block of store to consider
-  filename    source file from which called
+  func        function from which called
   linenumber  line number in source file


 Returns:      nothing
 */


static void
-store_release_3(void * block, const char * filename, int linenumber)
+store_release_3(void * block, int pool, const char * func, int linenumber)
{
/* It will never be the first block, so no need to check that. */

-for (storeblock * b = chainbase[store_pool]; b; b = b->next)
+for (storeblock * b = chainbase[pool]; b; b = b->next)
   {
   storeblock * bb = b->next;
   if (bb && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
     {
+    int siz = bb->length + ALIGNED_SIZEOF_STOREBLOCK;
     b->next = bb->next;
-    pool_malloc -= bb->length + ALIGNED_SIZEOF_STOREBLOCK;
+    nbytes[pool] -= siz;
+    pool_malloc -= siz;
+    nblocks[pool]--;


     /* Cut out the debugging stuff for utilities, but stop picky compilers
     from giving warnings. */


 #ifdef COMPILE_UTILITY
-    filename = filename;
+    func = func;
     linenumber = linenumber;
 #else
     DEBUG(D_memory)
-      if (f.running_in_test_harness)
-        debug_printf("-Release       %d\n", pool_malloc);
-      else
-        debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, filename,
-          linenumber, pool_malloc);
+      debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, func,
+    linenumber, pool_malloc);


     if (f.running_in_test_harness)
       memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
@@ -502,20 +718,74 @@ Returns:    new location of data
 */


void *
-store_newblock_3(void * block, int newsize, int len,
- const char * filename, int linenumber)
+store_newblock_3(void * block, BOOL tainted, int newsize, int len,
+ const char * func, int linenumber)
{
-BOOL release_ok = store_last_get[store_pool] == block;
-uschar * newtext = store_get(newsize);
+int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
+BOOL release_ok = !tainted && store_last_get[pool] == block;
+uschar * newtext;
+
+if (is_tainted(block) != tainted)
+ die_tainted(US"store_newblock", CUS func, linenumber);

+newtext = store_get(newsize, tainted);
memcpy(newtext, block, len);
-if (release_ok) store_release_3(block, filename, linenumber);
+if (release_ok) store_release_3(block, pool, func, linenumber);
return (void *)newtext;
}




+/******************************************************************************/
+static void *
+store_alloc_tail(void * yield, int size, const char * func, int line,
+  const uschar * type)
+{
+if ((nonpool_malloc += size) > max_nonpool_malloc)
+  max_nonpool_malloc = nonpool_malloc;
+
+/* Cut out the debugging stuff for utilities, but stop picky compilers from
+giving warnings. */
+
+#ifdef COMPILE_UTILITY
+func = func; line = line; type = type;
+#else
+
+/* If running in test harness, spend time making sure all the new store
+is not filled with zeros so as to catch problems. */
+
+if (f.running_in_test_harness)
+  memset(yield, 0xF0, (size_t)size);
+DEBUG(D_memory) debug_printf("--%6s %6p %5d bytes\t%-14s %4d\tpool %5d  nonpool %5d\n",
+  type, yield, size, func, line, pool_malloc, nonpool_malloc);
+#endif  /* COMPILE_UTILITY */
+
+return yield;
+}
+
+/*************************************************
+*                Mmap store                      *
+*************************************************/
+
+static void *
+store_mmap(int size, const char * func, int line)
+{
+void * yield, * top;
+
+if (size < 16) size = 16;
+
+if (!(yield = mmap(NULL, (size_t)size,
+          PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)))
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to mmap %d bytes of memory: "
+    "called from line %d of %s", size, line, func);
+
+if (yield < tainted_base) tainted_base = yield;
+if ((top = yield + size) > tainted_top) tainted_top = top;
+
+return store_alloc_tail(yield, size, func, line, US"Mmap");
+}
+
 /*************************************************
 *                Malloc store                    *
 *************************************************/
@@ -526,50 +796,32 @@ function is called via the macro store_malloc().


 Arguments:
   size        amount of store wanted
-  filename    source file from which called
+  func        function from which called
   linenumber  line number in source file


 Returns:      pointer to gotten store (panic on failure)
 */


-void *
-store_malloc_3(int size, const char *filename, int linenumber)
+static void *
+internal_store_malloc(int size, const char *func, int linenumber)
{
-void *yield;
+void * yield;

if (size < 16) size = 16;

 if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
-    "called from line %d of %s", size, linenumber, filename);
-
-nonpool_malloc += size;
-
-/* Cut out the debugging stuff for utilities, but stop picky compilers from
-giving warnings. */
-
-#ifdef COMPILE_UTILITY
-filename = filename;
-linenumber = linenumber;
-#else
-
-/* If running in test harness, spend time making sure all the new store
-is not filled with zeros so as to catch problems. */
+    "called from line %d in %s", size, linenumber, func);


-if (f.running_in_test_harness)
-  {
-  memset(yield, 0xF0, (size_t)size);
-  DEBUG(D_memory) debug_printf("--Malloc %5d %d %d\n", size, pool_malloc,
-    nonpool_malloc);
-  }
-else
-  {
-  DEBUG(D_memory) debug_printf("--Malloc %6p %5d %-14s %4d %d %d\n", yield,
-    size, filename, linenumber, pool_malloc, nonpool_malloc);
-  }
-#endif  /* COMPILE_UTILITY */
+return store_alloc_tail(yield, size, func, linenumber, US"Malloc");
+}


-return yield;
+void *
+store_malloc_3(int size, const char *func, int linenumber)
+{
+if (n_nonpool_blocks++ > max_nonpool_blocks)
+ max_nonpool_blocks = n_nonpool_blocks;
+return internal_store_malloc(size, func, linenumber);
}


@@ -581,28 +833,48 @@ return yield;

 Arguments:
   block       block of store to free
-  filename    source file from which called
+  func        function from which called
   linenumber  line number in source file


 Returns:      nothing
 */


-void
-store_free_3(void *block, const char *filename, int linenumber)
+static void
+internal_store_free(void *block, const char *func, int linenumber)
 {
 #ifdef COMPILE_UTILITY
-filename = filename;
+func = func;
 linenumber = linenumber;
 #else
 DEBUG(D_memory)
-  {
-  if (f.running_in_test_harness)
-    debug_printf("----Free\n");
-  else
-    debug_printf("----Free %6p %-20s %4d\n", block, filename, linenumber);
-  }
+  debug_printf("----Free %6p %-20s %4d\n", block, func, linenumber);
 #endif  /* COMPILE_UTILITY */
 free(block);
 }


+void
+store_free_3(void *block, const char *func, int linenumber)
+{
+n_nonpool_blocks--;
+internal_store_free(block, func, linenumber);
+}
+
+/******************************************************************************/
+/* Stats output on process exit */
+void
+store_exit(void)
+{
+#ifndef COMPILE_UTILITY
+DEBUG(D_memory)
+ {
+ debug_printf("----Exit nonpool max: %3d kB in %d blocks\n",
+  (max_nonpool_malloc+1023)/1024, max_nonpool_blocks);
+ debug_printf("----Exit npools  max: %3d kB\n", max_pool_malloc/1024);
+ for (int i = 0; i < NPOOLS; i++)
+  debug_printf("----Exit  pool %d max: %3d kB in %d blocks\t%s %s\n",
+    i, maxbytes[i]/1024, maxblocks[i], poolclass[i], pooluse[i]);
+ }
+#endif
+}
+
 /* End of store.c */
diff --git a/src/src/store.h b/src/src/store.h
index 393f6c7..6464e32 100644
--- a/src/src/store.h
+++ b/src/src/store.h
@@ -12,13 +12,15 @@


/* Define symbols for identifying the store pools. */

-enum { POOL_MAIN, POOL_PERM, POOL_SEARCH };
+enum { POOL_MAIN,       POOL_PERM,       POOL_SEARCH,
+       POOL_TAINT_BASE,
+       POOL_TAINT_MAIN = POOL_TAINT_BASE, POOL_TAINT_PERM, POOL_TAINT_SEARCH };


/* This variable (the one for the current pool) is set by store_get() to its
yield, and by store_reset() to NULL. This allows string_cat() to optimize its
store handling. */

-extern void *store_last_get[3];
+extern void *store_last_get[6];

/* This variable contains the current store pool number. */

@@ -27,28 +29,40 @@ extern int store_pool;
/* Macros for calling the memory allocation routines with
tracing information for debugging. */

-#define store_extend(addr,old,new) \
-  store_extend_3(addr, old, new, __FILE__, __LINE__)
-
-#define store_free(addr)     store_free_3(addr, __FILE__, __LINE__)
-#define store_get(size)      store_get_3(size, __FILE__, __LINE__)
-#define store_get_perm(size) store_get_perm_3(size, __FILE__, __LINE__)
-#define store_malloc(size)   store_malloc_3(size, __FILE__, __LINE__)
-#define store_newblock(addr,newsize,datalen) \
-                 store_newblock_3(addr, newsize, datalen, __FILE__, __LINE__)
-#define store_reset(addr)    store_reset_3(addr, __FILE__, __LINE__)
+#define store_extend(addr, tainted, old, new) \
+  store_extend_3(addr, tainted, old, new, __FUNCTION__, __LINE__)
+
+#define store_free(addr) \
+    store_free_3(addr, __FUNCTION__, __LINE__)
+#define store_get(size, tainted) \
+    store_get_3(size, tainted, __FUNCTION__, __LINE__)
+#define store_get_perm(size, tainted) \
+    store_get_perm_3(size, tainted, __FUNCTION__, __LINE__)
+#define store_malloc(size) \
+    store_malloc_3(size, __FUNCTION__, __LINE__)
+#define store_mark(void) \
+    store_mark_3(__FUNCTION__, __LINE__)
+#define store_newblock(addr, tainted, newsize, datalen) \
+    store_newblock_3(addr, tainted, newsize, datalen, __FUNCTION__, __LINE__)
+#define store_release_above(addr) \
+    store_release_above_3(addr, __FUNCTION__, __LINE__)
+#define store_reset(mark) \
+    store_reset_3(mark, store_pool, __FUNCTION__, __LINE__)



/* The real functions */
+typedef void ** rmark;

-/* The value of the 2nd arg is __FILE__ in every call, so give its correct type */
-extern BOOL    store_extend_3(void *, int, int, const char *, int);
+extern BOOL    is_tainted(const void *);
+extern BOOL    store_extend_3(void *, BOOL, int, int, const char *, int);
 extern void    store_free_3(void *, const char *, int);
-extern void   *store_get_3(int, const char *, int)    ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
-extern void   *store_get_perm_3(int, const char *, int)    ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
-extern void   *store_malloc_3(int, const char *, int)    ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
-extern void   *store_newblock_3(void *, int, int, const char *, int);
-extern void    store_reset_3(void *, const char *, int);
+extern void   *store_get_3(int, BOOL, const char *, int)    ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+extern void   *store_get_perm_3(int, BOOL, const char *, int)    ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+extern void   *store_malloc_3(int, const char *, int)        ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+extern rmark   store_mark_3(const char *, int);
+extern void   *store_newblock_3(void *, BOOL, int, int, const char *, int);
+extern void    store_release_above_3(void *, const char *, int);
+extern rmark   store_reset_3(rmark, int, const char *, int);


#endif /* STORE_H */

diff --git a/src/src/string.c b/src/src/string.c
index fed2100..8cc34a0 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -12,6 +12,7 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */
#include "exim.h"
#include <assert.h>

+static void gstring_rebuffer(gstring * g);

 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -167,7 +168,7 @@ Returns:      pointer to the buffer
 uschar *
 string_format_size(int size, uschar *buffer)
 {
-if (size == 0) Ustrcpy(buffer, "     ");
+if (size == 0) Ustrcpy(buffer, US"     ");
 else if (size < 1024) sprintf(CS buffer, "%5d", size);
 else if (size < 10*1024)
   sprintf(CS buffer, "%4.1fK", (double)size / 1024.0);
@@ -306,7 +307,7 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */


-ss = store_get(length + nonprintcount * 3 + 1);
+ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));

/* Copy everything, escaping non printers. */

@@ -362,7 +363,7 @@ p = Ustrchr(s, '\\');
if (!p) return s;

len = Ustrlen(s) + 1;
-ss = store_get(len);
+ss = store_get(len, is_tainted(s));

 q = ss;
 off = p - s;
@@ -412,22 +413,32 @@ return ss;
 *            Copy and save string                *
 *************************************************/


-/* This function assumes that memcpy() is faster than strcpy().
-
+/*
Argument: string to copy
-Returns: copy of string in new store
+Returns: copy of string in new store with the same taint status
*/

uschar *
string_copy_function(const uschar *s)
{
+return string_copy_taint(s, is_tainted(s));
+}
+
+/* This function assumes that memcpy() is faster than strcpy().
+As above, but explicitly specifying the result taint status
+*/
+
+uschar *
+string_copy_taint(const uschar * s, BOOL tainted)
+{
int len = Ustrlen(s) + 1;
-uschar *ss = store_get(len);
+uschar *ss = store_get(len, tainted);
memcpy(ss, s, len);
return ss;
}


+
 /*************************************************
 *       Copy and save string, given length       *
 *************************************************/
@@ -445,7 +456,7 @@ Returns:    copy of string in new store
 uschar *
 string_copyn_function(const uschar *s, int n)
 {
-uschar *ss = store_get(n + 1);
+uschar *ss = store_get(n + 1, is_tainted(s));
 Ustrncpy(ss, s, n);
 ss[n] = 0;
 return ss;
@@ -555,7 +566,7 @@ uschar *
 string_copy_dnsdomain(uschar *s)
 {
 uschar *yield;
-uschar *ss = yield = store_get(Ustrlen(s) + 1);
+uschar *ss = yield = store_get(Ustrlen(s) + 1, is_tainted(s));


while (*s != 0)
{
@@ -617,25 +628,22 @@ else

/* Get enough store to copy into */

-t = yield = store_get(s - *sptr + 1);
+t = yield = store_get(s - *sptr + 1, is_tainted(*sptr));
s = *sptr;

/* Do the copy */

 if (*s != '\"')
-  {
   while (*s != 0 && !isspace(*s)) *t++ = *s++;
-  }
 else
   {
   s++;
   while (*s != 0 && *s != '\"')
     {
-    if (*s == '\\') *t++ = string_interpret_escape(&s);
-      else *t++ = *s;
+    *t++ = *s == '\\' ? string_interpret_escape(&s) : *s;
     s++;
     }
-  if (*s != 0) s++;
+  if (*s) s++;
   }


 /* Update the pointer and return the terminated copy */
@@ -664,35 +672,24 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 */


uschar *
-string_sprintf(const char *format, ...)
+string_sprintf_trc(const char *format, const uschar * func, unsigned line, ...)
{
-#ifdef COMPILE_UTILITY
-uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
-gstring g = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
-gstring * gp = &g;
-#else
-gstring * gp = string_get(STRING_SPRINTF_BUFFER_SIZE);
-#endif
-gstring * gp2;
+gstring * g;
va_list ap;

-va_start(ap, format);
-gp2 = string_vformat(gp, FALSE, format, ap);
-gp->s[gp->ptr] = '\0';
+va_start(ap, line);
+g = string_vformat_trc(NULL, func, line, STRING_SPRINTF_BUFFER_SIZE,
+    SVFMT_REBUFFER|SVFMT_EXTEND, format, ap);
 va_end(ap);


-if (!gp2)
+if (!g)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "string_sprintf expansion was longer than %d; format string was (%s)\n"
-    "expansion started '%.32s'",
-    gp->size, format, gp->s);
+    " called from %s %d\n",
+    STRING_SPRINTF_BUFFER_SIZE, format, func, line);


-#ifdef COMPILE_UTILITY
-return string_copy(gp->s);
-#else
-gstring_release_unused(gp);
-return gp->s;
-#endif
+gstring_release_unused(g);
+return string_from_gstring(g);
}


@@ -801,7 +798,7 @@ return NULL;
#ifdef COMPILE_UTILITY
/* Dummy version for this function; it should never be called */
static void
-gstring_grow(gstring * g, int p, int count)
+gstring_grow(gstring * g, int count)
{
assert(FALSE);
}
@@ -1047,18 +1044,21 @@ return list;


/************************************************/
-/* Add more space to a growable-string.
+/* Add more space to a growable-string. The caller should check
+first if growth is required. The gstring struct is modified on
+return; specifically, the string-base-pointer may have been changed.

 Arguments:
   g        the growable-string
-  p        current end of data
-  count        amount to grow by
+  count        amount needed for g->ptr to increase by
 */


static void
-gstring_grow(gstring * g, int p, int count)
+gstring_grow(gstring * g, int count)
{
+int p = g->ptr;
int oldsize = g->size;
+BOOL tainted = is_tainted(g->s);

/* Mostly, string_cat() is used to build small strings of a few hundred
characters at most. There are times, however, when the strings are very much
@@ -1067,7 +1067,9 @@ To try to keep things reasonable, we use increments whose size depends on the
existing length of the string. */

 unsigned inc = oldsize < 4096 ? 127 : 1023;
-g->size = ((p + count + inc) & ~inc) + 1;
+
+if (count <= 0) return;
+g->size = (p + count + inc + 1) & ~inc;        /* one for a NUL */


/* Try to extend an existing allocation. If the result of calling
store_extend() is false, either there isn't room in the current memory block,
@@ -1079,8 +1081,8 @@ is at its start.) However, we can do this only if we know that the old string
was the last item on the dynamic memory stack. This is the case if it matches
store_last_get. */

-if (!store_extend(g->s, oldsize, g->size))
- g->s = store_newblock(g->s, g->size, p);
+if (!store_extend(g->s, tainted, oldsize, g->size))
+ g->s = store_newblock(g->s, tainted, g->size, p);
}


@@ -1113,17 +1115,20 @@ gstring *
string_catn(gstring * g, const uschar *s, int count)
{
int p;
+BOOL srctaint = is_tainted(s);

if (!g)
{
unsigned inc = count < 4096 ? 127 : 1023;
unsigned size = ((count + inc) & ~inc) + 1;
- g = string_get(size);
+ g = string_get_tainted(size, srctaint);
}
+else if (srctaint && !is_tainted(g->s))
+ gstring_rebuffer(g);

p = g->ptr;
if (p + count >= g->size)
- gstring_grow(g, p, count);
+ gstring_grow(g, count);

 /* Because we always specify the exact number of characters to copy, we can
 use memcpy(), which is likely to be more efficient than strncopy() because the
@@ -1207,12 +1212,14 @@ Returns:       TRUE if the result fitted in the buffer
 */


 BOOL
-string_format(uschar * buffer, int buflen, const char * format, ...)
+string_format_trc(uschar * buffer, int buflen,
+  const uschar * func, unsigned line, const char * format, ...)
 {
 gstring g = { .size = buflen, .ptr = 0, .s = buffer }, *gp;
 va_list ap;
 va_start(ap, format);
-gp = string_vformat(&g, FALSE, format, ap);
+gp = string_vformat_trc(&g, func, line, STRING_SPRINTF_BUFFER_SIZE,
+    0, format, ap);
 va_end(ap);
 g.s[g.ptr] = '\0';
 return !!gp;
@@ -1220,14 +1227,24 @@ return !!gp;




+/* Copy the content of a string to tainted memory */
+static void
+gstring_rebuffer(gstring * g)
+{
+uschar * s = store_get(g->size, TRUE);
+memcpy(s, g->s, g->ptr);
+g->s = s;
+}
+


-/* Bulid or append to a growing-string, sprintf-style.
+/* Build or append to a growing-string, sprintf-style.

-If the "extend" argument is true, the string passed in can be NULL,
-empty, or non-empty.
+If the "extend" flag is true, the string passed in can be NULL,
+empty, or non-empty. Growing is subject to an overall limit given
+by the size_limit argument.

-If the "extend" argument is false, the string passed in may not be NULL,
+If the "extend" flag is false, the string passed in may not be NULL,
will not be grown, and is usable in the original place after return.
The return value can be NULL to signify overflow.

@@ -1236,24 +1253,35 @@ not nul-terminated.
*/

gstring *
-string_vformat(gstring * g, BOOL extend, const char *format, va_list ap)
+string_vformat_trc(gstring * g, const uschar * func, unsigned line,
+ unsigned size_limit, unsigned flags, const char *format, va_list ap)
{
enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };

-int width, precision, off, lim;
+int width, precision, off, lim, need;
 const char * fp = format;    /* Deliberately not unsigned */
+BOOL dest_tainted = FALSE;


 string_datestamp_offset = -1;    /* Datestamp not inserted */
 string_datestamp_length = 0;    /* Datestamp not inserted */
 string_datestamp_type = 0;    /* Datestamp not inserted */


#ifdef COMPILE_UTILITY
-assert(!extend);
+assert(!(flags & SVFMT_EXTEND));
assert(g);
#else

 /* Ensure we have a string, to save on checking later */
 if (!g) g = string_get(16);
+else if (!(flags & SVFMT_TAINT_NOCHK)) dest_tainted = is_tainted(g->s);
+
+if (!(flags & SVFMT_TAINT_NOCHK) && !dest_tainted && is_tainted(format))
+  {
+  if (!(flags & SVFMT_REBUFFER))
+    die_tainted(US"string_vformat", func, line);
+  gstring_rebuffer(g);
+  dest_tainted = TRUE;
+  }
 #endif    /*!COMPILE_UTILITY*/


 lim = g->size - 1;    /* leave one for a nul */
@@ -1276,10 +1304,10 @@ while (*fp)
   if (*fp != '%')
     {
     /* Avoid string_copyn() due to COMPILE_UTILITY */
-    if (g->ptr >= lim - 1)
+    if ((need = g->ptr + 1) > lim)
       {
-      if (!extend) return NULL;
-      gstring_grow(g, g->ptr, 1);
+      if (!(flags & SVFMT_EXTEND) || need > size_limit) return NULL;
+      gstring_grow(g, 1);
       lim = g->size - 1;
       }
     g->s[g->ptr++] = (uschar) *fp++;
@@ -1348,10 +1376,10 @@ while (*fp)
     case 'x':
     case 'X':
       width = length > L_LONG ? 24 : 12;
-      if (g->ptr >= lim - width)
+      if ((need = g->ptr + width) > lim)
     {
-    if (!extend) return NULL;
-    gstring_grow(g, g->ptr, width);
+    if (!(flags & SVFMT_EXTEND) || need >= size_limit) return NULL;
+    gstring_grow(g, width);
     lim = g->size - 1;
     gp = CS g->s + g->ptr;
     }
@@ -1378,10 +1406,10 @@ while (*fp)
     case 'p':
       {
       void * ptr;
-      if (g->ptr >= lim - 24)
+      if ((need = g->ptr + 24) > lim)
     {
-    if (!extend) return NULL;
-    gstring_grow(g, g->ptr, 24);
+    if (!(flags & SVFMT_EXTEND || need >= size_limit)) return NULL;
+    gstring_grow(g, 24);
     lim = g->size - 1;
     gp = CS g->s + g->ptr;
     }
@@ -1411,10 +1439,10 @@ while (*fp)
     case 'g':
     case 'G':
       if (precision < 0) precision = 6;
-      if (g->ptr >= lim - precision - 8)
+      if ((need = g->ptr + precision + 8) > lim)
     {
-    if (!extend) return NULL;
-    gstring_grow(g, g->ptr, precision+8);
+    if (!(flags & SVFMT_EXTEND || need >= size_limit)) return NULL;
+    gstring_grow(g, precision+8);
     lim = g->size - 1;
     gp = CS g->s + g->ptr;
     }
@@ -1429,20 +1457,20 @@ while (*fp)
     /* String types */


     case '%':
-      if (g->ptr >= lim - 1)
+      if ((need = g->ptr + 1) > lim)
     {
-    if (!extend) return NULL;
-    gstring_grow(g, g->ptr, 1);
+    if (!(flags & SVFMT_EXTEND || need >= size_limit)) return NULL;
+    gstring_grow(g, 1);
     lim = g->size - 1;
     }
       g->s[g->ptr++] = (uschar) '%';
       break;


     case 'c':
-      if (g->ptr >= lim - 1)
+      if ((need = g->ptr + 1) > lim)
     {
-    if (!extend) return NULL;
-    gstring_grow(g, g->ptr, 1);
+    if (!(flags & SVFMT_EXTEND || need >= size_limit)) return NULL;
+    gstring_grow(g, 1);
     lim = g->size - 1;
     }
       g->s[g->ptr++] = (uschar) va_arg(ap, int);
@@ -1472,6 +1500,16 @@ while (*fp)
       if (!s) s = null;
       slen = Ustrlen(s);


+      if (!(flags & SVFMT_TAINT_NOCHK) && !dest_tainted && is_tainted(s))
+    if (flags & SVFMT_REBUFFER)
+      {
+      gstring_rebuffer(g);
+      gp = CS g->s + g->ptr;
+      dest_tainted = TRUE;
+      }
+    else
+      die_tainted(US"string_vformat", func, line);
+
     INSERT_STRING:              /* Come to from %D or %M above */


       {
@@ -1497,10 +1535,10 @@ while (*fp)
       else
     width = precision = slen;


-      if (!extend)
+      if ((need = g->ptr + width) >= size_limit || !(flags & SVFMT_EXTEND))
     {
     if (g->ptr == lim) return NULL;
-    if (g->ptr >= lim - width)
+    if (need > lim)
       {
       truncated = TRUE;
       width = precision = lim - g->ptr - 1;
@@ -1508,9 +1546,9 @@ while (*fp)
       if (precision < 0) precision = 0;
       }
     }
-      else if (g->ptr >= lim - width)
+      else if (need > lim)
     {
-    gstring_grow(g, g->ptr, width - (lim - g->ptr));
+    gstring_grow(g, width);
     lim = g->size - 1;
     gp = CS g->s + g->ptr;
     }
@@ -1536,25 +1574,15 @@ while (*fp)
     }
   }


+if (g->ptr > g->size)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "string_format internal error: caller %s %d", func, line);
 return g;
 }




 #ifndef COMPILE_UTILITY
-
-gstring *
-string_fmt_append(gstring * g, const char *format, ...)
-{
-va_list ap;
-va_start(ap, format);
-g = string_vformat(g, TRUE, format, ap);
-va_end(ap);
-return g;
-}
-
-
-
 /*************************************************
 *       Generate an "open failed" message        *
 *************************************************/
@@ -1572,7 +1600,8 @@ Returns:        a message, in dynamic store
 */


uschar *
-string_open_failed(int eno, const char *format, ...)
+string_open_failed_trc(int eno, const uschar * func, unsigned line,
+ const char *format, ...)
{
va_list ap;
gstring * g = string_get(1024);
@@ -1585,7 +1614,8 @@ specified messages. If it does, the message just gets truncated, and there
doesn't seem much we can do about that. */

 va_start(ap, format);
-(void) string_vformat(g, FALSE, format, ap);
+(void) string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
+    0, format, ap);
 string_from_gstring(g);
 gstring_release_unused(g);
 va_end(ap);
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 423c3a2..ca60ddb 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -648,7 +648,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
     }


   m.size = statbuf.st_size;
-  if (!(m.data = malloc(m.size)))
+  if (!(m.data = store_malloc(m.size)))
     {
     fclose(fp);
     return tls_error_sys(US"malloc failed", errno, NULL, errstr);
@@ -657,13 +657,13 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
     {
     saved_errno = errno;
     fclose(fp);
-    free(m.data);
+    store_free(m.data);
     return tls_error_sys(US"fread failed", saved_errno, NULL, errstr);
     }
   fclose(fp);


   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
-  free(m.data);
+  store_free(m.data);
   if (rc)
     return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
   DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
@@ -736,25 +736,25 @@ if (rc < 0)
     return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
           rc, host, errstr);
   m.size = sz;
-  if (!(m.data = malloc(m.size)))
+  if (!(m.data = store_malloc(m.size)))
     return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);


   /* this will return a size 1 less than the allocation size above */
   if ((rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz)))
     {
-    free(m.data);
+    store_free(m.data);
     return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
     }
   m.size = sz; /* shrink by 1, probably */


   if ((sz = write_to_fd_buf(fd, m.data, (size_t) m.size)) != m.size)
     {
-    free(m.data);
+    store_free(m.data);
     return tls_error_sys(US"TLS cache write D-H params failed",
         errno, NULL, errstr);
     }
-  free(m.data);
+  store_free(m.data);
   if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
     return tls_error_sys(US"TLS cache write D-H params final newline failed",
         errno, NULL, errstr);
@@ -1332,7 +1332,7 @@ if (host)
   several in parallel. */
   int old_pool = store_pool;
   store_pool = POOL_PERM;
-  state = store_get(sizeof(exim_gnutls_state_st));
+  state = store_get(sizeof(exim_gnutls_state_st), FALSE);
   store_pool = old_pool;


   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
@@ -1629,7 +1629,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
   exim_gnutls_peer_err(US"getting size for cert DN failed");
   return FAIL; /* should not happen */
   }
-dn_buf = store_get_perm(sz);
+dn_buf = store_get_perm(sz, TRUE);    /* tainted */
 rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
 exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");


@@ -1709,8 +1709,8 @@ else
       for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
       nrec++;


-      dd = store_get(nrec * sizeof(uschar *));
-      ddl = store_get(nrec * sizeof(int));
+      dd = store_get(nrec * sizeof(uschar *), FALSE);
+      ddl = store_get(nrec * sizeof(int), FALSE);
       nrec--;


       if ((rc = dane_state_init(&s, 0)))
@@ -2392,8 +2392,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
      rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
     ) if (rr->type == T_TLSA) i++;


-dane_data = store_get(i * sizeof(uschar *));
-dane_data_len = store_get(i * sizeof(int));
+dane_data = store_get(i * sizeof(uschar *), FALSE);
+dane_data_len = store_get(i * sizeof(int), FALSE);

 i = 0;
 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
@@ -2401,6 +2401,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
     ) if (rr->type == T_TLSA && rr->size > 3)
   {
   const uschar * p = rr->data;
+/*XXX need somehow to mark rr and its data as tainted.  Doues this mean copying it? */
   uint8_t usage = p[0], sel = p[1], type = p[2];


   DEBUG(D_tls)
@@ -2505,7 +2506,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       {
       open_db dbblock, * dbm_file;
       int dlen = sizeof(dbdata_tls_session) + tkt.size;
-      dbdata_tls_session * dt = store_get(dlen);
+      dbdata_tls_session * dt = store_get(dlen, TRUE);


       DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
       memcpy(dt->session, tkt.data, tkt.size);
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index ea30ff7..9542a1e 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2790,7 +2790,7 @@ if (SSL_SESSION_is_resumable(ss))     /* 1.1.1 */
   {
   int len = i2d_SSL_SESSION(ss, NULL);
   int dlen = sizeof(dbdata_tls_session) + len;
-  dbdata_tls_session * dt = store_get(dlen);
+  dbdata_tls_session * dt = store_get(dlen, TRUE);
   uschar * s = dt->session;
   open_db dbblock, * dbm_file;


@@ -2908,7 +2908,7 @@ BOOL require_ocsp = FALSE;

rc = store_pool;
store_pool = POOL_PERM;
-exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE);
exim_client_ctx->corked = NULL;
store_pool = rc;

diff --git a/src/src/tlscert-gnu.c b/src/src/tlscert-gnu.c
index cebeae5..4513581 100644
--- a/src/src/tlscert-gnu.c
+++ b/src/src/tlscert-gnu.c
@@ -25,7 +25,7 @@ int
tls_export_cert(uschar * buf, size_t buflen, void * cert)
{
size_t sz = buflen;
-void * reset_point = store_get(0);
+rmark reset_point = store_mark();
int fail;
const uschar * cp;

@@ -49,7 +49,7 @@ return fail;
int
tls_import_cert(const uschar * buf, void ** cert)
{
-void * reset_point = store_get(0);
+rmark reset_point = store_mark();
gnutls_datum_t datum;
gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
int fail = 0;
@@ -112,7 +112,7 @@ size_t len = 32;
if (mod && Ustrcmp(mod, "int") == 0)
return string_sprintf("%u", (unsigned)t);

-cp = store_get(len);
+cp = store_get(len, FALSE);
 if (f.timestamps_utc)
   {
   uschar * tz = to_tz(US"GMT0");
@@ -146,7 +146,7 @@ if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gi0", __FUNCTION__, ret);


-cp = store_get(siz);
+cp = store_get(siz, TRUE);
if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) < 0)
return g_err("gi1", __FUNCTION__, ret);

@@ -200,7 +200,7 @@ if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gs0", __FUNCTION__, ret);


-cp1 = store_get(len*4+1);
+cp1 = store_get(len*4+1, TRUE);
if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len) != 0)
return g_err("gs1", __FUNCTION__, ret);

@@ -230,7 +230,7 @@ if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gs0", __FUNCTION__, ret);


-cp = store_get(siz);
+cp = store_get(siz, TRUE);
if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0)
return g_err("gs1", __FUNCTION__, ret);

@@ -258,7 +258,7 @@ ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER)
return g_err("ge0", __FUNCTION__, ret);

-cp1 = store_get(siz*4 + 1);
+cp1 = store_get(siz*4 + 1, TRUE);

 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
   CS oid, idx, CS cp1, &siz, &crit);
@@ -314,7 +314,7 @@ for (int index = 0;; index++)
       return g_err("gs0", __FUNCTION__, ret);
     }


-  ele = store_get(siz+1);
+  ele = store_get(siz+1, TRUE);
   if ((ret = gnutls_x509_crt_get_subject_alt_name(
     (gnutls_x509_crt_t)cert, index, ele, &siz, NULL)) < 0)
     return g_err("gs1", __FUNCTION__, ret);
@@ -397,7 +397,7 @@ for (int index = 0;; index++)
       return g_err("gc0", __FUNCTION__, ret);
     }


-  ele = store_get(siz);
+  ele = store_get(siz, TRUE);
   if ((ret = gnutls_x509_crt_get_crl_dist_points(
       (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0)
     return g_err("gc1", __FUNCTION__, ret);
@@ -420,7 +420,7 @@ int fail;


 if (  (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
     GNUTLS_X509_FMT_DER, cp, &len)) != GNUTLS_E_SHORT_MEMORY_BUFFER
-   || !(cp = store_get((int)len))
+   || !(cp = store_get((int)len, TRUE), TRUE)    /* tainted */
    || (fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
         GNUTLS_X509_FMT_DER, cp, &len))
    )
@@ -445,7 +445,7 @@ if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, NULL, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gf0", __FUNCTION__, ret);


-cp = store_get(siz*3+1);
+cp = store_get(siz*3+1, TRUE);
if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0)
return g_err("gf1", __FUNCTION__, ret);

diff --git a/src/src/tlscert-openssl.c b/src/src/tlscert-openssl.c
index f9808b3..0aa65c8 100644
--- a/src/src/tlscert-openssl.c
+++ b/src/src/tlscert-openssl.c
@@ -65,7 +65,7 @@ return fail;
int
tls_import_cert(const uschar * buf, void ** cert)
{
-void * reset_point = store_get(0);
+rmark reset_point = store_mark();
const uschar * cp = string_unprinting(US buf);
BIO * bp;
X509 * x = *(X509 **)cert;
@@ -172,7 +172,7 @@ else

       /* convert to string in our format */
       len = 32;
-      s = store_get(len);
+      s = store_get(len, FALSE);
       strftime(CS s, (size_t)len, "%b %e %T %Y %z", tm_p);
       }
     }
@@ -336,7 +336,7 @@ M_ASN1_OCTET_STRING_print(bp, adata);
 /* binary data, DER encoded */
 /* just dump for now */
 len = BIO_get_mem_data(bp, &cp1);
-cp3 = cp2 = store_get(len*3+1);
+cp3 = cp2 = store_get(len*3+1, TRUE);


while(len)
{
@@ -503,7 +503,7 @@ if (!X509_digest(cert,fdig,md,&n))
expand_string_message = US"tls_cert_fprt: out of mem\n";
return NULL;
}
-cp = store_get(n*2+1);
+cp = store_get(n*2+1, TRUE);
for (int j = 0; j < (int)n; j++) sprintf(CS cp+2*j, "%02X", md[j]);
return(cp);
}
diff --git a/src/src/transport.c b/src/src/transport.c
index d745ef1..ce02adb 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -375,8 +375,11 @@ transport_ctx tctx = {{0}};
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
va_list ap;

+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that the result will never be expanded. */
+
va_start(ap, format);
-if (!string_vformat(&gs, FALSE, format, ap))
+if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
va_end(ap);
tctx.u.fd = fd;
@@ -638,7 +641,7 @@ so that we don't handle it again. */

for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;

-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
ppp->next = *pdlist;
*pdlist = ppp;
ppp->ptr = p;
@@ -662,7 +665,7 @@ if (ppp) return TRUE;

/* Remember what we have output, and output it. */

-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
 ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
@@ -742,7 +745,7 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
     {
     if (tblock && tblock->rewrite_rules)
       {
-      void *reset_point = store_get(0);
+      rmark reset_point = store_mark();
       header_line *hh;


       if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
@@ -949,7 +952,7 @@ if (!(tctx->options & topt_no_headers))
     BOOL first = TRUE;
     struct aci *plist = NULL;
     struct aci *dlist = NULL;
-    void *reset_point = store_get(0);
+    rmark reset_point = store_mark();


     if (!write_chunk(tctx, US"Envelope-to: ", 13)) goto bad;


@@ -1252,7 +1255,7 @@ if ((write_pid = fork()) == 0)
         != sizeof(int)
      )
     rc = FALSE;    /* compiler quietening */
-  _exit(0);
+  exim_underbar_exit(0);
   }
 save_errno = errno;


@@ -1498,7 +1501,7 @@ for (host_item * host = hostlist; host; host = host->next)

   if (!(host_record = dbfn_read(dbm_file, host->name)))
     {
-    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
+    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH, FALSE);
     host_record->count = host_record->sequence = 0;
     }


@@ -1557,7 +1560,7 @@ for (host_item * host = hostlist; host; host = host->next)
   else
     {
     dbdata_wait *newr =
-      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH);
+      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH, FALSE);
     memcpy(newr, host_record, sizeof(dbdata_wait) + host_length);
     host_record = newr;
     }
@@ -1692,7 +1695,7 @@ while (1)


/* create an array to read entire message queue into memory for processing */

- msgq = store_malloc(sizeof(msgq_t) * host_record->count);
+ msgq = store_get(sizeof(msgq_t) * host_record->count, FALSE);
msgq_count = host_record->count;
msgq_actual = msgq_count;

@@ -1700,7 +1703,7 @@ while (1)
     {
     msgq[i].bKeep = TRUE;


-    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
+    Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), 
       MESSAGE_ID_LENGTH);
     msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
     }
@@ -1728,7 +1731,7 @@ while (1)
       msgq[i].bKeep = FALSE;
     else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
       {
-      Ustrcpy(new_message_id, msgq[i].message_id);
+      Ustrcpy_nt(new_message_id, msgq[i].message_id);
       msgq[i].bKeep = FALSE;
       bFound = TRUE;
       break;
@@ -1798,10 +1801,7 @@ while (1)
     }


   if (bFound)        /* Usual exit from main loop */
-    {
-    store_free (msgq);
     break;
-    }


   /* If host_length <= 0 we have emptied a record and not found a good message,
   and there are no continuation records. Otherwise there is a continuation
@@ -1824,8 +1824,6 @@ while (1)
     dbfn_close(dbm_file);
     return FALSE;
     }
-
-  store_free(msgq);
   }        /* we need to process a continuation record */


/* Control gets here when an existing message has been encountered; its
@@ -2018,7 +2016,7 @@ delivery batch option is set. */

for (address_item * ad = addr; ad; ad = ad->next) address_count++;
max_args = address_count + 60;
-*argvptr = argv = store_get((max_args+1)*sizeof(uschar *));
+*argvptr = argv = store_get((max_args+1)*sizeof(uschar *), FALSE);

/* Split the command up into arguments terminated by white space. Lose
trailing space at the start and end. Double-quoted arguments can contain \\ and
@@ -2028,18 +2026,19 @@ arguments are verbatim. Copy each argument into a new string. */
s = cmd;
while (isspace(*s)) s++;

-while (*s != 0 && argcount < max_args)
+for (; *s != 0 && argcount < max_args; argcount++)
   {
   if (*s == '\'')
     {
     ss = s + 1;
     while (*ss != 0 && *ss != '\'') ss++;
-    argv[argcount++] = ss = store_get(ss - s++);
+    argv[argcount] = ss = store_get(ss - s++, is_tainted(cmd));
     while (*s != 0 && *s != '\'') *ss++ = *s++;
     if (*s != 0) s++;
     *ss++ = 0;
     }
-  else argv[argcount++] = string_copy(string_dequote(CUSS &s));
+  else
+    argv[argcount] = string_dequote(CUSS &s);
   while (isspace(*s)) s++;
   }


@@ -2079,8 +2078,8 @@ $recipients. */
 DEBUG(D_transport)
   {
   debug_printf("direct command:\n");
-  for (int i = 0; argv[i] != US 0; i++)
-    debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
+  for (int i = 0; argv[i]; i++)
+    debug_printf("  argv[%d] = '%s'\n", i, string_printing(argv[i]));
   }


 if (expand_arguments)
@@ -2133,6 +2132,7 @@ if (expand_arguments)
       int address_pipe_argcount = 0;
       int address_pipe_max_args;
       uschar **address_pipe_argv;
+      BOOL tainted;


       /* We can never have more then the argv we will be loading into */
       address_pipe_max_args = max_args - argcount + 1;
@@ -2141,10 +2141,11 @@ if (expand_arguments)
         debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args);


       /* We allocate an additional for (uschar *)0 */
-      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *));
+      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), FALSE);


       /* +1 because addr->local_part[0] == '|' since af_force_command is set */
       s = expand_string(addr->local_part + 1);
+      tainted = is_tainted(s);


       if (s == NULL || *s == '\0')
         {
@@ -2163,7 +2164,7 @@ if (expand_arguments)
           {
           ss = s + 1;
           while (*ss != 0 && *ss != '\'') ss++;
-          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++);
+          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++, tainted);
           while (*s != 0 && *s != '\'') *ss++ = *s++;
           if (*s != 0) s++;
           *ss++ = 0;
@@ -2242,12 +2243,12 @@ if (expand_arguments)
       expanded_arg = expand_cstring(argv[i]);
       f.enable_dollar_recipients = FALSE;


-      if (expanded_arg == NULL)
+      if (!expanded_arg)
         {
         uschar *msg = string_sprintf("Expansion of \"%s\" "
           "from command \"%s\" in %s failed: %s",
           argv[i], cmd, etext, expand_string_message);
-        if (addr != NULL)
+        if (addr)
           {
           addr->transport_return = expand_failed;
           addr->message = msg;
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index d9f8d49..5d23008 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -613,11 +613,11 @@ notify_comsat(uschar *user, off_t offset)
 {
 struct servent *sp;
 host_item host;
-uschar buffer[256];
+uschar * s;


DEBUG(D_transport) debug_printf("notify_comsat called\n");

-sprintf(CS buffer, "%.200s@" OFF_T_FMT "\n", user, offset);
+s = string_sprintf("%.200s@" OFF_T_FMT "\n", user, offset);

if ((sp = getservbyname("biff", "udp")) == NULL)
{
@@ -658,7 +658,7 @@ for (host_item * h = &host; h; h = h->next)
/* Connect never fails for a UDP socket, so don't set a timeout. */

(void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, NULL);
- rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
+ rc = send(sock, s, Ustrlen(s) + 1, 0);
(void)close(sock);

   if (rc >= 0) break;
@@ -765,7 +765,7 @@ Returns:        the sum of the sizes of the stattable files
 */


off_t
-check_dir_size(uschar *dirname, int *countptr, const pcre *regex)
+check_dir_size(const uschar * dirname, int *countptr, const pcre *regex)
{
DIR *dir;
off_t sum = 0;
@@ -778,8 +778,7 @@ if (dir == NULL) return 0;

while ((ent = readdir(dir)) != NULL)
{
- uschar *name = US ent->d_name;
- uschar buffer[1024];
+ uschar * path, * name = US ent->d_name;

if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;

@@ -787,7 +786,7 @@ while ((ent = readdir(dir)) != NULL)

/* If there's a regex, try to find the size using it */

-  if (regex != NULL)
+  if (regex)
     {
     int ovector[6];
     if (pcre_exec(regex, NULL, CS name, Ustrlen(name), 0, 0, ovector,6) >= 2)
@@ -809,18 +808,12 @@ while ((ent = readdir(dir)) != NULL)


/* No regex or no match for the regex, or captured non-digits */

-  if (!string_format(buffer, sizeof(buffer), "%s/%s", dirname, name))
-    {
-    DEBUG(D_transport)
-      debug_printf("check_dir_size: name too long: dir=%s name=%s\n", dirname,
-        name);
-    continue;
-    }
+  path = string_sprintf("%s/%s", dirname, name);


-  if (Ustat(buffer, &statbuf) < 0)
+  if (Ustat(path, &statbuf) < 0)
     {
     DEBUG(D_transport)
-      debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, buffer,
+      debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, path,
         strerror(errno));
     continue;
     }
@@ -828,7 +821,7 @@ while ((ent = readdir(dir)) != NULL)
   if ((statbuf.st_mode & S_IFMT) == S_IFREG)
     sum += statbuf.st_size;
   else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
-    sum += check_dir_size(buffer, &count, regex);
+    sum += check_dir_size(path, &count, regex);
   }


 closedir(dir);
@@ -3091,7 +3084,7 @@ if (yield != OK)
     addr->message = string_sprintf("mailbox is full "
       "(quota exceeded while writing to file %s)", filename);
     #else
-    addr->message = string_sprintf("mailbox is full");
+    addr->message = US"mailbox is full";
     #endif  /* EDQUOT */
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
@@ -3291,7 +3284,7 @@ else
           uschar *iptr = expand_string(nametag);
           if (iptr != NULL)
             {
-            uschar *etag = store_get(Ustrlen(iptr) + 2);
+            uschar *etag = store_get(Ustrlen(iptr) + 2, is_tainted(iptr));
             uschar *optr = etag;
             while (*iptr != 0)
               {
diff --git a/src/src/transports/appendfile.h b/src/src/transports/appendfile.h
index 4b14db1..4f0f126 100644
--- a/src/src/transports/appendfile.h
+++ b/src/src/transports/appendfile.h
@@ -94,6 +94,6 @@ extern void appendfile_transport_init(transport_instance *);


/* Function that is shared with tf_maildir.c */

-extern off_t check_dir_size(uschar *, int *, const pcre *);
+extern off_t check_dir_size(const uschar *, int *, const pcre *);

/* End of transports/appendfile.h */
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 9decfba..734e658 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -446,7 +446,7 @@ if (oncelog && *oncelog != 0 && to)

     cache_size = statbuf.st_size;
     add_size = sizeof(time_t) + Ustrlen(to) + 1;
-    cache_buff = store_get(cache_size + add_size);
+    cache_buff = store_get(cache_size + add_size, is_tainted(oncelog));


     if (read(cache_fd, cache_buff, cache_size) != cache_size)
       {
@@ -821,48 +821,26 @@ if (logfile)
   int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
   if (log_fd >= 0)
     {
-    uschar *ptr = log_buffer;
+    gstring gs = { .size = LOG_BUFFER_SIZE, .ptr = 0, .s = log_buffer }, *g = &gs;
+
+    /* Use taint-unchecked routines for writing into log_buffer, trusting
+    that we'll never expand it. */
+
     DEBUG(D_transport) debug_printf("logging message details\n");
-    sprintf(CS ptr, "%s\n", tod_stamp(tod_log));
-    while(*ptr) ptr++;
+    g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "%s\n", tod_stamp(tod_log));
     if (from)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  From: %s\n", from);
-      while(*ptr) ptr++;
-      }
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  From: %s\n", from);
     if (to)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  To: %s\n", to);
-      while(*ptr) ptr++;
-      }
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  To: %s\n", to);
     if (cc)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  Cc: %s\n", cc);
-      while(*ptr) ptr++;
-      }
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  Cc: %s\n", cc);
     if (bcc)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  Bcc: %s\n", bcc);
-      while(*ptr) ptr++;
-      }
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  Bcc: %s\n", bcc);
     if (subject)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  Subject: %s\n", subject);
-      while(*ptr) ptr++;
-      }
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  Subject: %s\n", subject);
     if (headers)
-      {
-      (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
-        "  %s\n", headers);
-      while(*ptr) ptr++;
-      }
-    if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
-      || close(log_fd))
+      g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  %s\n", headers);
+    if(write(log_fd, g->s, g->ptr) != g->ptr || close(log_fd))
       DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
         "transport\n", logfile, tblock->name);
     }
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index b2bf5f0..306ec45 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -175,7 +175,7 @@ if (*errno_value == ERRNO_CHHEADER_FAIL)


if (*errno_value == ERRNO_WRITEINCOMPLETE)
{
- *message = string_sprintf("failed to write a data block");
+ *message = US"failed to write a data block";
return FALSE;
}

@@ -228,8 +228,11 @@ gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
int rc;
va_list ap;

+/*XXX see comment in smtp_write_command() regarding leaving stuff in
+big_buffer */
+
va_start(ap, format);
-if (!string_vformat(&gs, FALSE, CS format, ap))
+if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, CS format, ap))
{
va_end(ap);
errno = ERRNO_SMTPFORMAT;
@@ -553,7 +556,7 @@ allows for message+recipient checks after the message has been received. */

/* First thing is to wait for an initial greeting. */

-Ustrcpy(big_buffer, "initial connection");
+Ustrcpy(big_buffer, US"initial connection");
if (!lmtp_read_response(out, buffer, sizeof(buffer), '2',
timeout)) goto RESPONSE_FAILED;

@@ -641,7 +644,7 @@ if (send_data)

   sigalrm_seen = FALSE;
   transport_write_timeout = timeout;
-  Ustrcpy(big_buffer, "sending data block");   /* For error messages */
+  Ustrcpy(big_buffer, US"sending data block");   /* For error messages */
   DEBUG(D_transport|D_v)
     debug_printf("  LMTP>> writing message and terminating \".\"\n");


@@ -657,7 +660,7 @@ if (send_data)
     goto RESPONSE_FAILED;
     }


- Ustrcpy(big_buffer, "end of data"); /* For error messages */
+ Ustrcpy(big_buffer, US"end of data"); /* For error messages */

   /* We now expect a response for every address that was accepted above,
   in the same order. For those that get a response, their status is fixed;
@@ -763,9 +766,9 @@ if (errno == ERRNO_CHHEADER_FAIL)
     string_sprintf("Failed to expand headers_add or headers_remove: %s",
       expand_string_message);
 else if (errno == ERRNO_FILTER_FAIL)
-  addrlist->message = string_sprintf("Filter process failure");
+  addrlist->message = US"Filter process failure";
 else if (errno == ERRNO_WRITEINCOMPLETE)
-  addrlist->message = string_sprintf("Failed repeatedly to write data");
+  addrlist->message = US"Failed repeatedly to write data";
 else if (errno == ERRNO_SMTPFORMAT)
   addrlist->message = US"overlong LMTP command generated";
 else
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 8e5c0c5..70df872 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -463,7 +463,7 @@ set_up_shell_command(const uschar ***argvptr, uschar *cmd,
 {
 const uschar **argv;


-*argvptr = argv = store_get((4)*sizeof(uschar *));
+*argvptr = argv = store_get((4)*sizeof(uschar *), FALSE);

 argv[0] = US"/bin/sh";
 argv[1] = US"-c";
@@ -938,7 +938,7 @@ if (!written_ok)
       addr->more_errno,
       (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
     else if (errno == ERRNO_WRITEINCOMPLETE)
-      addr->message = string_sprintf("Failed repeatedly to write data");
+      addr->message = US"Failed repeatedly to write data";
     else
       addr->message = string_sprintf("Error %d", errno);
     return FALSE;
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index ad0de52..617a55a 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -620,7 +620,7 @@ switch(*errno_value)
     return FALSE;


   case ERRNO_WRITEINCOMPLETE:    /* failure to write a complete data block */
-    *message = string_sprintf("failed to write a data block");
+    *message = US"failed to write a data block";
     return FALSE;


#ifdef SUPPORT_I18N
@@ -1184,8 +1184,14 @@ while (count-- > 0)

   else if (errno != 0 || sx->buffer[0] == 0)
     {
-    string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
+    gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer }, * g = &gs;
+
+    /* Use taint-unchecked routines for writing into big_buffer, trusting
+    that we'll never expand it. */
+
+    g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
       transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
+    string_from_gstring(g);
     return -2;
     }


@@ -1555,20 +1561,20 @@ Globals        f.smtp_authenticated
 Return    True on error, otherwise buffer has (possibly empty) terminated string
 */


-BOOL
+static BOOL
 smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
             smtp_transport_options_block *ob)
 {
-uschar *local_authenticated_sender = authenticated_sender;
+uschar * local_authenticated_sender = authenticated_sender;


#ifdef notdef
debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
#endif

-if (ob->authenticated_sender != NULL)
+if (ob->authenticated_sender)
   {
   uschar *new = expand_string(ob->authenticated_sender);
-  if (new == NULL)
+  if (!new)
     {
     if (!f.expand_string_forcedfail)
       {
@@ -1578,17 +1584,17 @@ if (ob->authenticated_sender != NULL)
       return TRUE;
       }
     }
-  else if (new[0] != 0) local_authenticated_sender = new;
+  else if (*new) local_authenticated_sender = new;
   }


/* Add the authenticated sender address if present */

-if ((f.smtp_authenticated || ob->authenticated_sender_force) &&
-    local_authenticated_sender != NULL)
+if (  (f.smtp_authenticated || ob->authenticated_sender_force)
+   && local_authenticated_sender)
   {
-  string_format(buffer, bufsize, " AUTH=%s",
+  string_format_nt(buffer, bufsize, " AUTH=%s",
     auth_xtextencode(local_authenticated_sender,
-    Ustrlen(local_authenticated_sender)));
+      Ustrlen(local_authenticated_sender)));
   client_authenticated_sender = string_copy(local_authenticated_sender);
   }
 else
@@ -3040,7 +3046,7 @@ if (  sx->peer_offered & OPTION_UTF8
    && addrlist->prop.utf8_msg
    && !addrlist->prop.utf8_downcvt
    )
-  Ustrcpy(p, " SMTPUTF8"), p += 9;
+  Ustrcpy(p, US" SMTPUTF8"), p += 9;
 #endif


 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
@@ -3061,9 +3067,9 @@ for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
 if (sx->peer_offered & OPTION_DSN && !sx->dsn_all_lasthop)
   {
   if (dsn_ret == dsn_ret_hdrs)
-    { Ustrcpy(p, " RET=HDRS"); p += 9; }
+    { Ustrcpy(p, US" RET=HDRS"); p += 9; }
   else if (dsn_ret == dsn_ret_full)
-    { Ustrcpy(p, " RET=FULL"); p += 9; }
+    { Ustrcpy(p, US" RET=FULL"); p += 9; }


   if (dsn_envid)
     {
@@ -3100,7 +3106,7 @@ if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
     {
     BOOL first = TRUE;


-    Ustrcpy(p, " NOTIFY=");
+    Ustrcpy(p, US" NOTIFY=");
     while (*p) p++;
     for (int i = 0; i < nelem(rf_list); i++) if (addr->dsn_flags & rf_list[i])
       {
@@ -4598,6 +4604,17 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     else
       if (ob->hosts_randomize) s = expanded_hosts = string_copy(s);


+    if (is_tainted(s))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+    "attempt to use tainted host list '%s' from '%s' in transport %s",
+    s, ob->hosts, tblock->name);
+      /* Avoid leaking info to an attacker */
+      addrlist->message = US"internal configuration error";
+      addrlist->transport_return = PANIC;
+      return FALSE;
+      }
+
     host_build_hostlist(&hostlist, s, ob->hosts_randomize);


     /* Check that the expansion yielded something useful. */
@@ -5082,7 +5099,7 @@ retry_non_continued:


       if (expanded_hosts)
     {
-    thost = store_get(sizeof(host_item));
+    thost = store_get(sizeof(host_item), FALSE);
     *thost = *host;
     thost->name = string_copy(host->name);
     thost->address = string_copy(host->address);
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 79d674f..ac56209 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -198,9 +198,6 @@ extern void smtp_transport_closedown(transport_instance *);




-extern BOOL    smtp_mail_auth_str(uschar *, unsigned,
-         address_item *, smtp_transport_options_block *);
-
 #ifdef SUPPORT_SOCKS
 extern int     socks_sock_connect(host_item *, int, int, uschar *,
              transport_instance *, int);
diff --git a/src/src/transports/tf_maildir.c b/src/src/transports/tf_maildir.c
index 4caf0cd..611895e 100644
--- a/src/src/transports/tf_maildir.c
+++ b/src/src/transports/tf_maildir.c
@@ -258,13 +258,12 @@ off_t sum = 0;
 struct dirent *ent;
 struct stat statbuf;


-dir = opendir(CS path);
-if (dir == NULL) return 0;
+if (!(dir = opendir(CS path)))
+ return 0;

-while ((ent = readdir(dir)) != NULL)
+while ((ent = readdir(dir)))
{
- uschar *name = US ent->d_name;
- uschar buffer[1024];
+ uschar * s, * name = US ent->d_name;

if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;

@@ -282,26 +281,19 @@ while ((ent = readdir(dir)) != NULL)

/* The name is OK; stat it. */

-  if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
-    {
-    DEBUG(D_transport)
-      debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
-        path, name);
-    continue;
-    }
-
-  if (Ustat(buffer, &statbuf) < 0)
+  s = string_sprintf("%s/%s", path, name);
+  if (Ustat(s, &statbuf) < 0)
     {
     DEBUG(D_transport)
       debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
-        buffer, strerror(errno));
+        s, strerror(errno));
     continue;
     }


   if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
     {
     DEBUG(D_transport)
-      debug_printf("skipping %s/%s: not a directory\n", path, name);
+      debug_printf("skipping %s/%s: not a directory\n", s, name);
     continue;
     }


@@ -312,18 +304,14 @@ while ((ent = readdir(dir)) != NULL)
/* If this is a maildir folder, call this function recursively. */

   if (name[0] == '.')
-    {
-    sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
+    sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
       timestamp_only);
-    }


/* Otherwise it must be a folder that contains messages (e.g. new or cur), so
we need to get its size, unless all we are interested in is the timestamp. */

   else if (!timestamp_only)
-    {
-    sum += check_dir_size(buffer, filecount, regex);
-    }
+    sum += check_dir_size(s, filecount, regex);
   }


closedir(dir);
@@ -392,8 +380,7 @@ the same thing. */
filename = string_sprintf("%s/maildirsize", path);

DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
-fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600);
-if (fd < 0)
+if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
{
if (errno != ENOENT) return -1;
DEBUG(D_transport)
@@ -405,8 +392,7 @@ if (fd < 0)
still correct, and that the size of the file is still small enough. If so,
compute the maildir size from the file. */

-count = read(fd, buffer, sizeof(buffer));
-if (count >= sizeof(buffer))
+if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
   {
   DEBUG(D_transport)
     debug_printf("maildirsize file too big (%d): recalculating\n", count);
diff --git a/src/src/tree.c b/src/src/tree.c
index ff792bb..d5a4096 100644
--- a/src/src/tree.c
+++ b/src/src/tree.c
@@ -29,10 +29,11 @@ Returns:  nothing
 void
 tree_add_nonrecipient(uschar *s)
 {
-tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s));
+rmark rpoint = store_mark();
+tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s));
 Ustrcpy(node->name, s);
 node->data.ptr = NULL;
-if (!tree_insertnode(&tree_nonrecipients, node)) store_reset(node);
+if (!tree_insertnode(&tree_nonrecipients, node)) store_reset(rpoint);
 }



@@ -53,10 +54,11 @@ Returns: nothing
void
tree_add_duplicate(uschar *s, address_item *addr)
{
-tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s));
+rmark rpoint = store_mark();
+tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s));
Ustrcpy(node->name, s);
node->data.ptr = addr;
-if (!tree_insertnode(&tree_duplicates, node)) store_reset(node);
+if (!tree_insertnode(&tree_duplicates, node)) store_reset(rpoint);
}


@@ -74,14 +76,16 @@ Returns:     nothing
 void
 tree_add_unusable(host_item *h)
 {
+rmark rpoint = store_mark();
 tree_node *node;
 uschar s[256];
 sprintf(CS s, "T:%.200s:%s", h->name, h->address);
-node = store_get(sizeof(tree_node) + Ustrlen(s));
+node = store_get(sizeof(tree_node) + Ustrlen(s),
+            is_tainted(h->name) || is_tainted(h->address));
 Ustrcpy(node->name, s);
 node->data.val = h->why;
 if (h->status == hstatus_unusable_expired) node->data.val += 256;
-if (!tree_insertnode(&tree_unusable, node)) store_reset(node);
+if (!tree_insertnode(&tree_unusable, node)) store_reset(rpoint);
 }



@@ -369,7 +373,7 @@ static void
tree_add_var(uschar * name, uschar * val, void * ctx)
{
tree_node ** root = ctx;
-tree_node * node = store_get(sizeof(tree_node) + Ustrlen(name));
+tree_node * node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name));
Ustrcpy(node->name, name);
node->data.ptr = val;
(void) tree_insertnode(root, node);
diff --git a/src/src/utf8.c b/src/src/utf8.c
index dec7228..529a9a6 100644
--- a/src/src/utf8.c
+++ b/src/src/utf8.c
@@ -143,7 +143,7 @@ if (!string_is_utf8(utf8)) return string_copy(utf8);

 p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len);
 p_len = ucs4_len*4;    /* this multiplier is pure guesswork */
-res = store_get(p_len+5);
+res = store_get(p_len+5, is_tainted(utf8));


res[0] = 'x'; res[1] = 'n'; res[2] = res[3] = '-';

@@ -172,7 +172,7 @@ uschar * s, * res;
DEBUG(D_expand) debug_printf("l_a2u: '%s'\n", alabel);
alabel += 4;
p_len = Ustrlen(alabel);
-p = (punycode_uint *) store_get((p_len+1) * sizeof(*p));
+p = store_get((p_len+1) * sizeof(*p), is_tainted(alabel));

 if ((rc = punycode_decode(p_len, CCS alabel, &p_len, p, NULL)) != PUNYCODE_SUCCESS)
   {
diff --git a/src/src/verify.c b/src/src/verify.c
index 25a4a0c..e98dee6 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -98,7 +98,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
   {
   if (length == sizeof(dbdata_callout_cache_obs))
     {
-    dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache));
+    dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache), FALSE);
     memcpy(new, cache_record, length);
     new->postmaster_stamp = new->random_stamp = new->time_stamp;
     cache_record = new;
@@ -420,7 +420,7 @@ if (addr->transport == cutthrough.addr.transport)


     if (done)
       {
-      address_item * na = store_get(sizeof(address_item));
+      address_item * na = store_get(sizeof(address_item), FALSE);
       *na = cutthrough.addr;
       cutthrough.addr = *addr;
       cutthrough.addr.host_used = &cutthrough.host;
@@ -976,8 +976,7 @@ no_conn:
     {
     extern int acl_where;    /* src/acl.c */
     errno = 0;
-    addr->message = string_sprintf(
-        "response to \"EHLO\" did not include SMTPUTF8");
+    addr->message = US"response to \"EHLO\" did not include SMTPUTF8";
     addr->user_message = acl_where == ACL_WHERE_RCPT
       ? US"533 no support for internationalised mailbox name"
       : US"550 mailbox unavailable";
@@ -1089,7 +1088,7 @@ no_conn:
       for (address_item * caddr = &cutthrough.addr, * parent = addr->parent;
        parent;
        caddr = caddr->parent, parent = parent->parent)
-        *(caddr->parent = store_get(sizeof(address_item))) = *parent;
+        *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent;


       ctctx.outblock.buffer = ctbuffer;
       ctctx.outblock.buffersize = sizeof(ctbuffer);
@@ -3396,9 +3395,9 @@ else


   else
     {    /* Set up a tree entry to cache the lookup */
-    t = store_get(sizeof(tree_node) + Ustrlen(query));
+    t = store_get(sizeof(tree_node) + Ustrlen(query), is_tainted(query));
     Ustrcpy(t->name, query);
-    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
+    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
     (void)tree_insertnode(&dnsbl_cache, t);
     }


diff --git a/src/src/version.c b/src/src/version.c
index 91d8124..118ebbd 100644
--- a/src/src/version.c
+++ b/src/src/version.c
@@ -51,7 +51,7 @@ version_date[0] = 0;
Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, sizeof(date_buffer));

#else
-Ustrcpy(today, __DATE__);
+Ustrcpy(today, US __DATE__);
if (today[4] == ' ') today[4] = '0';
today[3] = today[6] = '-';

@@ -60,8 +60,8 @@ version_date[0] = 0;
Ustrncat(version_date, today+4, 3);
Ustrncat(version_date, today, 4);
Ustrncat(version_date, today+7, 4);
-Ustrcat(version_date, " ");
-Ustrcat(version_date, __TIME__);
+Ustrcat(version_date, US" ");
+Ustrcat(version_date, US __TIME__);
#endif
}

diff --git a/test/confs/0102 b/test/confs/0102
index dde11a5..2497fd0 100644
--- a/test/confs/0102
+++ b/test/confs/0102
@@ -25,7 +25,9 @@ localuser:
   local_part_prefix = bsmtp_ : mmdf_
   local_part_prefix_optional
   retry_use_local_part
-  transport = ${local_part_prefix}local_delivery
+  transport = ${if !def:local_part_prefix {} \
+        {${if eq {bsmtp_}{$local_part_prefix} {bsmtp_}\
+            {${if eq {mmdf_}{$local_part_prefix} {mmdf_} {}}}}}}local_delivery



# ----- Transports -----
diff --git a/test/confs/0137 b/test/confs/0137
index 1a2094e..7c48d62 100644
--- a/test/confs/0137
+++ b/test/confs/0137
@@ -8,6 +8,7 @@
log_selector = +subject
domainlist local_domains = test.ex
qualify_domain = test.ex
+untrusted_set_sender = *


 # ----- Routers -----
@@ -17,7 +18,10 @@ begin routers
 smart:
   driver = accept
   retry_use_local_part
-  transport = $h_transport:
+  transport = ${if eq {t1}{$sender_address_local_part} {local_delivery} \
+        {${if eq {t2}{$sender_address_local_part} {local_delivery_fcntl} \
+        {${if eq {t3}{$sender_address_local_part} {local_delivery_fcntl_blocking} \
+        {} }}}}}



# ----- Transports -----
diff --git a/test/confs/0140 b/test/confs/0140
index fbc64b4..0256715 100644
--- a/test/confs/0140
+++ b/test/confs/0140
@@ -22,7 +22,7 @@ reply:
driver = accept
retry_use_local_part
senders = !
- transport = $local_part
+ transport = ${if eq {reply1}{$local_part} {reply1}{reply2}}


 # ----- Transports -----
diff --git a/test/confs/0284 b/test/confs/0284
index d38f383..0103742 100644
--- a/test/confs/0284
+++ b/test/confs/0284
@@ -16,7 +16,9 @@ begin routers
 all:
   driver = accept
   address_data = ${if match{$local_part}{^(.)}{$1}}
-  transport = ${if match{$local_part}{^.*-(.*)\$}{$1}fail}
+  transport = ${if eq {${substr_-1_1:$local_part}}{1} {t1} \
+        {${if eq {${substr_-1_1:$local_part}}{2} {t2} \
+          {${if eq {${substr_-1_1:$local_part}}{3} {t3} {t4}}}}}}



# ----- Transports -----
diff --git a/test/confs/0386 b/test/confs/0386
index 5eecfcc..0af3202 100644
--- a/test/confs/0386
+++ b/test/confs/0386
@@ -6,11 +6,22 @@ primary_hostname = myhost.test.ex

# ----- Main settings -----

-acl_smtp_rcpt = DIR/aux-fixed/TESTNUM.acl$local_part
+acl_smtp_rcpt = chk_rcpt
qualify_domain = test.ex
trusted_users = CALLER


+# ----- ACL -----
+begin acl
+
+chk_rcpt:
+  accept    local_parts = 1
+        endpass
+        acl = DIR/aux-fixed/TESTNUM.acl1
+  accept    local_parts = 2
+        endpass
+        acl = DIR/aux-fixed/TESTNUM.acl2
+
 # ----- Routers -----


begin routers
diff --git a/test/confs/0428 b/test/confs/0428
index 38601f3..5ee5cdb 100644
--- a/test/confs/0428
+++ b/test/confs/0428
@@ -36,16 +36,112 @@ r1:
user = CALLER
file_transport = t1

-r2:
+r2_8:
+  driver = redirect
+  local_parts = userx8
+  allow_filter
+  data = #Sieve filter\n \
+    require["fileinto","comparator-i;ascii-numeric"]; \
+        if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
+          fileinto "inbox.JUNK"; \
+          stop; \
+        }
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2_9:
+  driver = redirect
+  local_parts = userx9
+  allow_filter
+  data = #Sieve filter\n \
+    require["fileinto","comparator-i;ascii-numeric"]; \
+        if header :comparator "i;ascii-numeric" "X-Sieve" "98" { \
+          fileinto "inbox.JUNK"; \
+          stop; \
+        }
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2_10:
+  driver = redirect
+  local_parts = userx10
+  allow_filter
+  data = #Sieve filter\n \
+     require["fileinto","comparator-i;ascii-numeric"]; \
+        if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
+          fileinto "inbox.JUNK"; \
+          stop; \
+        }
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2_11:
   driver = redirect
+  local_parts = userx11
+  allow_filter
+  data = #Sieve filter\n \
+     require["fileinto","comparator-i;ascii-numeric"]; \
+        if header :comparator "i;ascii-numeric" "X-Sieve" "-99" { \
+          fileinto "inbox.JUNK"; \
+          stop; \
+        }
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2_12:
+  driver = redirect
+  local_parts = userx12
+  allow_filter
+  data = #Sieve filter\n \
+     require["fileinto","comparator-i;ascii-numeric"]; \
+        if header :comparator "i;ascii-numeric" "X-Sieve" "-98" { \
+          fileinto "inbox.JUNK"; \
+          stop; \
+        }
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2_13:
+  driver = redirect
+  local_parts = userx13 : someone13
+  allow_filter
+  data = #Sieve filter\n \
+     require ["vacation"];  \
+        vacation "I am gone.  Not here.";
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+  sieve_vacation_directory = DIR/test-vacation-directory
+
+r2_14:
+  driver = redirect
+  local_parts = userx14
   local_part_suffix = -*
   local_part_suffix_optional
   allow_filter
+  data = #Sieve filter\n \
+     require ["envelope","fileinto"];  \
+        if envelope :matches :localpart "to" "*-suffix" { \
+          fileinto "userx-sawsuffix"; \
+          stop;  \
+        }   
+  user = CALLER
+  file_transport = t1
+  reply_transport = t3
+
+r2:
+  driver = redirect
+  allow_filter
+  skip_syntax_errors
   data = "#Sieve filter\n$h_filter:"
   user = CALLER
   file_transport = t1
   reply_transport = t3
-  sieve_vacation_directory = DIR/test-vacation-directory



 # ----- Transports -----
diff --git a/test/confs/0478 b/test/confs/0478
index d9fd0a5..cfb9a17 100644
--- a/test/confs/0478
+++ b/test/confs/0478
@@ -27,7 +27,9 @@ begin transports
 t1:
   driver = smtp
   port = PORT_D
-  hosts = ${if eq {$sender_host_address}{}{$local_part}{V4NET.0.0.2}}
+  hosts = ${if !eq {$sender_host_address}{} {V4NET.0.0.2} \
+        {${if eq {127.0.0.1}{$local_part} {127.0.0.1} \
+        {${if eq {V4NET.0.0.1}{$local_part} {V4NET.0.0.1}}}}}}
   allow_localhost
   connect_timeout = 1s


diff --git a/test/confs/0504 b/test/confs/0504
index b2dadfc..c7b0131 100644
--- a/test/confs/0504
+++ b/test/confs/0504
@@ -13,7 +13,7 @@ begin routers

r1:
driver = accept
- transport = $h_transport:
+ transport = OPT


# ----- Transports -----
diff --git a/test/confs/0610 b/test/confs/0610
index 0567d8a..4977835 100644
--- a/test/confs/0610
+++ b/test/confs/0610
@@ -28,9 +28,15 @@ conn_chk:

begin routers

-client:
+r1:
driver = accept
- transport = $sender_address_local_part
+ condition = ${if eq {t1}{$sender_address_local_part}}
+ transport = t1
+
+r2:
+ driver = accept
+ condition = ${if eq {t2}{$sender_address_local_part}}
+ transport = t2

# ----- Transports -----

diff --git a/test/confs/1003 b/test/confs/1003
index bdbd2e6..d15a892 100644
--- a/test/confs/1003
+++ b/test/confs/1003
@@ -21,7 +21,11 @@ begin routers
 client:
   driver = accept
   condition = ${if eq {SERVER}{server}{no}{yes}}
-  transport = send_to_server
+  address_data = ${substr_1_1:$domain}
+  transport = send_to_server${if eq {1}{$address_data} {1} \
+                {${if eq {2}{$address_data} {2} \
+                {${if eq {3}{$address_data} {3} \
+                {${if eq {4}{$address_data} {4}{5}}}}}}}}


server:
driver = accept
@@ -37,10 +41,45 @@ local_delivery:
file = DIR/test-mail/$local_part
user = CALLER

+send_to_server1:
+ driver = smtp
+ allow_localhost
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
+ port = PORT_D
+ interface = <; ::1 ; HOSTIPV4
+
+send_to_server2:
+ driver = smtp
+ allow_localhost
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
+ port = PORT_D
+ interface = <; HOSTIPV6 ; HOSTIPV4
+
+send_to_server3:
+ driver = smtp
+ allow_localhost
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
+ port = PORT_D
+ interface = <; ${if eq{0}{1}{HOSTIPV6}fail}
+
+send_to_server4:
+ driver = smtp
+ allow_localhost
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
+ port = PORT_D
+ interface = <; ${if eq{0}{1}{HOSTIPV6}{ }}
+
+send_to_server5:
+ driver = smtp
+ allow_localhost
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
+ port = PORT_D
+ interface = <; ${if
+
send_to_server:
driver = smtp
allow_localhost
- hosts = $h_hosts
+ hosts = ${if eq {$local_part}{user4} {127.0.0.1} {<; ::1}}
port = PORT_D
interface = ${expand:$h_interface:}

diff --git a/test/confs/5000 b/test/confs/5000
index e3328c6..de9b4f8 100644
--- a/test/confs/5000
+++ b/test/confs/5000
@@ -2,6 +2,8 @@

.include DIR/aux-var/std_conf_prefix

+OPT =
+
primary_hostname = myhost.test.ex

# ----- Main settings -----
@@ -14,7 +16,7 @@ begin routers
localuser:
driver = accept
local_parts = userx
- transport = $h_maildir:appendfile
+ transport = VALUE


 # ----- Transports -----
@@ -33,7 +35,7 @@ maildir_tagged_appendfile:
   directory = DIR/test-mail
   envelope_to_add
   maildir_format
-  maildir_tag = ${expand:$h_tag:}
+  maildir_tag = OPT
   message_prefix =
   quota = 20K
   quota_size_regex = S=(\d+)$
@@ -45,7 +47,7 @@ maildir_taggedX_appendfile:
   directory = DIR/test-mail
   envelope_to_add
   maildir_format
-  maildir_tag = ${expand:$h_tag:}
+  maildir_tag = OPT
   message_prefix =
   quota = 20K
   quota_size_regex = ,S=(\d+):
diff --git a/test/confs/5050 b/test/confs/5050
index 462d7dc..4258a72 100644
--- a/test/confs/5050
+++ b/test/confs/5050
@@ -19,7 +19,9 @@ localuser:
   local_part_prefix = bsmtp_ : mmdf_ : mbx_
   local_part_prefix_optional
   retry_use_local_part
-  transport = ${local_part_prefix}local_delivery
+  transport = ${if eq {bsmtp_} {${local_part_prefix}} {bsmtp_} \
+        {${if eq {mmdf_} {${local_part_prefix}} {mmdf_} \
+        {${if eq {mbx_} {${local_part_prefix}} {mbx_}{}}}}}}local_delivery



# ----- Transports -----
diff --git a/test/confs/5103 b/test/confs/5103
index 7212fb3..e9ac80c 100644
--- a/test/confs/5103
+++ b/test/confs/5103
@@ -13,7 +13,7 @@ begin routers

r1:
driver = accept
- transport = $h_transport:
+ transport = t1


# ----- Transports -----
diff --git a/test/log/0137 b/test/log/0137
index 30ee4cd..022c9de 100644
--- a/test/log/0137
+++ b/test/log/0137
@@ -1,19 +1,19 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss T="First"
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= t1@foo U=CALLER P=local S=sss T="First"
1999-03-02 09:44:33 10HmaX-0005vi-00 => userx <userx@???> R=smart T=local_delivery
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
-1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss T="Second"
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= t1@foo U=CALLER P=local S=sss T="Second"
1999-03-02 09:44:33 10HmaY-0005vi-00 == userx@??? R=smart T=local_delivery defer (-9): failed to lock mailbox TESTSUITE/test-mail/userx (lock file)
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
1999-03-02 09:44:33 10HmaY-0005vi-00 => userx <userx@???> R=smart T=local_delivery
1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss T="Third"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= t2@foo U=CALLER P=local S=sss T="Third"
1999-03-02 09:44:33 10HmaZ-0005vi-00 == userx@??? R=smart T=local_delivery_fcntl defer (-9): failed to lock mailbox TESTSUITE/test-mail/userx (fcntl/flock)
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
1999-03-02 09:44:33 10HmaZ-0005vi-00 => userx <userx@???> R=smart T=local_delivery_fcntl
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss T="Fourth"
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= t3@foo U=CALLER P=local S=sss T="Fourth"
1999-03-02 09:44:33 10HmbA-0005vi-00 == userx@??? R=smart T=local_delivery_fcntl_blocking defer (-9): failed to lock mailbox TESTSUITE/test-mail/userx (fcntl/flock)
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
1999-03-02 09:44:33 10HmbA-0005vi-00 => userx <userx@???> R=smart T=local_delivery_fcntl_blocking
diff --git a/test/log/0428 b/test/log/0428
index c33ef32..a1dfd78 100644
--- a/test/log/0428
+++ b/test/log/0428
@@ -26,28 +26,28 @@
1999-03-02 09:44:33 10HmbF-0005vi-00 => TESTSUITE/test-mail/userx <userx@???> R=r2 T=t1
1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbG-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbG-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx8@???> R=r2_8 T=t1
1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbH-0005vi-00 => TESTSUITE/test-mail/userx <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbH-0005vi-00 => TESTSUITE/test-mail/userx9 <userx9@???> R=r2_9 T=t1
1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbI-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbI-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx10@???> R=r2_10 T=t1
1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbJ-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx11@???> R=r2_11 T=t1
1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed
1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbK-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbK-0005vi-00 => TESTSUITE/test-mail/inbox.JUNK <userx12@???> R=r2_12 T=t1
1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
1999-03-02 09:44:33 10HmbL-0005vi-00 <= someone@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbL-0005vi-00 => TESTSUITE/test-mail/userx <userx@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbL-0005vi-00 => TESTSUITE/test-mail/userx13 <userx13@???> R=r2_13 T=t1
1999-03-02 09:44:33 10HmbM-0005vi-00 <= <> R=10HmbL-0005vi-00 U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmbM-0005vi-00 => someone <someone@???> R=rb T=t2
1999-03-02 09:44:33 10HmbM-0005vi-00 Completed
-1999-03-02 09:44:33 10HmbL-0005vi-00 => >someone@??? <userx@???> R=r2 T=t3
+1999-03-02 09:44:33 10HmbL-0005vi-00 => >someone@??? <userx13@???> R=r2_13 T=t3
1999-03-02 09:44:33 10HmbL-0005vi-00 Completed
1999-03-02 09:44:33 10HmbN-0005vi-00 <= someone@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbN-0005vi-00 => TESTSUITE/test-mail/userx <userx-suffix2@???> R=r2 T=t1
-1999-03-02 09:44:33 10HmbN-0005vi-00 => TESTSUITE/test-mail/userx-sawsuffix <userx-suffix@???> R=r2 T=t1
+1999-03-02 09:44:33 10HmbN-0005vi-00 => TESTSUITE/test-mail/userx14 <userx14-suffix2@???> R=r2_14 T=t1
+1999-03-02 09:44:33 10HmbN-0005vi-00 => TESTSUITE/test-mail/userx-sawsuffix <userx14-suffix@???> R=r2_14 T=t1
1999-03-02 09:44:33 10HmbN-0005vi-00 Completed
diff --git a/test/log/0610 b/test/log/0610
index 0502509..d741a86 100644
--- a/test/log/0610
+++ b/test/log/0610
@@ -2,9 +2,9 @@
1999-03-02 09:44:33 10HmaY-0005vi-00 <= t2@??? U=CALLER P=local-smtp S=sss
1999-03-02 09:44:33 Start queue run: pid=pppp -qq
1999-03-02 09:44:33 10HmaX-0005vi-00 H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
-1999-03-02 09:44:33 10HmaX-0005vi-00 == fred@??? R=client T=t1 defer (0) H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
+1999-03-02 09:44:33 10HmaX-0005vi-00 == fred@??? R=r1 T=t1 defer (0) H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
1999-03-02 09:44:33 10HmaY-0005vi-00 H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
-1999-03-02 09:44:33 10HmaY-0005vi-00 == fred@??? R=client T=t2 defer (0) H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
+1999-03-02 09:44:33 10HmaY-0005vi-00 == fred@??? R=r2 T=t2 defer (0) H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after initial connection: 451 Temporary local problem - please try later
1999-03-02 09:44:33 End queue run: pid=pppp -qq

******** SERVER ********
diff --git a/test/log/1003 b/test/log/1003
index b70b6d1..2ecb766 100644
--- a/test/log/1003
+++ b/test/log/1003
@@ -4,15 +4,15 @@
1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaY-0005vi-00 => userx@??? R=client T=send_to_server H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => user4@??? R=client T=send_to_server1 H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmbC-0005vi-00"
1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
-1999-03-02 09:44:33 10HmaZ-0005vi-00 => userx@??? R=client T=send_to_server H=::1 [::1] C="250 OK id=10HmbD-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => user6@??? R=client T=send_to_server2 H=::1 [::1] C="250 OK id=10HmbD-0005vi-00"
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
-1999-03-02 09:44:33 10HmbA-0005vi-00 => userx@??? R=client T=send_to_server H=::1 [::1] C="250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 => user6@??? R=client T=send_to_server3 H=::1 [::1] C="250 OK id=10HmbE-0005vi-00"
1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
-1999-03-02 09:44:33 10HmbB-0005vi-00 => userx@??? R=client T=send_to_server H=::1 [::1] C="250 OK id=10HmbF-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 => user6@??? R=client T=send_to_server4 H=::1 [::1] C="250 OK id=10HmbF-0005vi-00"
1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=client T=send_to_server defer (-1): failed to expand "interface" option for send_to_server transport: internal expansion of "<; ${if" failed: condition name expected, but found ""
+1999-03-02 09:44:33 10HmaX-0005vi-00 == user6@??? R=client T=send_to_server5 defer (-1): failed to expand "interface" option for send_to_server5 transport: condition name expected, but found ""
1999-03-02 09:44:33 End queue run: pid=pppp -qf

 ******** SERVER ********
diff --git a/test/log/5000 b/test/log/5000
index 89f91b8..68bedae 100644
--- a/test/log/5000
+++ b/test/log/5000
@@ -16,7 +16,7 @@
 1999-03-02 09:44:33 10HmbD-0005vi-00 => userx <userx@???> R=localuser T=maildir_tagged_appendfile
 1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${expand:$h_tag:}" (maildir_tag for maildir_tagged_appendfile transport) failed: internal expansion of "${if eq{0}{1}{rhubarb}" failed: syntax error in "if" item - "fail" expected
+1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${if eq{0}{1}{rhubarb}" (maildir_tag for maildir_tagged_appendfile transport) failed: syntax error in "if" item - "fail" expected
 1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
 1999-03-02 09:44:33 10HmbE-0005vi-00 => userx <userx@???> R=localuser T=maildir_tagged_appendfile
 1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
diff --git a/test/mail/0137.userx b/test/mail/0137.userx
index c446982..cfcf6e2 100644
--- a/test/mail/0137.userx
+++ b/test/mail/0137.userx
@@ -1,52 +1,56 @@
-From CALLER@??? Tue Mar 02 09:44:33 1999
+From t1@foo Tue Mar 02 09:44:33 1999
 Received: from CALLER by the.local.host.name with local (Exim x.yz)
-    (envelope-from <CALLER@???>)
+    (envelope-from <t1@foo>)
     id 10HmaX-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
 Subject: First
-Transport: local_delivery
 Message-Id: <E10HmaX-0005vi-00@???>
-From: CALLER_NAME <CALLER@???>
+From: CALLER_NAME <t1@foo>
+Sender: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+local_delivery
First message.

-From CALLER@??? Tue Mar 02 09:44:33 1999
+From t1@foo Tue Mar 02 09:44:33 1999
 Received: from CALLER by the.local.host.name with local (Exim x.yz)
-    (envelope-from <CALLER@???>)
+    (envelope-from <t1@foo>)
     id 10HmaY-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
 Subject: Second
-Transport: local_delivery
 Message-Id: <E10HmaY-0005vi-00@???>
-From: CALLER_NAME <CALLER@???>
+From: CALLER_NAME <t1@foo>
+Sender: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+local_delivery
Second message

-From CALLER@??? Tue Mar 02 09:44:33 1999
+From t2@foo Tue Mar 02 09:44:33 1999
 Received: from CALLER by the.local.host.name with local (Exim x.yz)
-    (envelope-from <CALLER@???>)
+    (envelope-from <t2@foo>)
     id 10HmaZ-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
 Subject: Third
-Transport: local_delivery_fcntl
 Message-Id: <E10HmaZ-0005vi-00@???>
-From: CALLER_NAME <CALLER@???>
+From: CALLER_NAME <t2@foo>
+Sender: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+local_delivery_fcntl
Third message

-From CALLER@??? Tue Mar 02 09:44:33 1999
+From t3@foo Tue Mar 02 09:44:33 1999
 Received: from CALLER by the.local.host.name with local (Exim x.yz)
-    (envelope-from <CALLER@???>)
+    (envelope-from <t3@foo>)
     id 10HmbA-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
 Subject: Fourth
-Transport: local_delivery_fcntl_blocking
 Message-Id: <E10HmbA-0005vi-00@???>
-From: CALLER_NAME <CALLER@???>
+From: CALLER_NAME <t3@foo>
+Sender: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+local_delivery_fcntl_blocking
Fourth message

diff --git a/test/mail/0428.inbox.JUNK b/test/mail/0428.inbox.JUNK
index 088f808..1ccfe98 100644
--- a/test/mail/0428.inbox.JUNK
+++ b/test/mail/0428.inbox.JUNK
@@ -1,80 +1,80 @@
 From CALLER@??? Tue Mar 02 09:44:33 1999
 Return-path: <CALLER@???>
-Envelope-to: userx@???
+Envelope-to: userx8@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbG-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
+    for userx8@???; Tue, 2 Mar 1999 09:44:33 +0000
 X-Sieve: 99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
-        if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
-          fileinto "inbox.JUNK";
-          stop;
-        }
 Message-Id: <E10HmbG-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require["fileinto","comparator-i;ascii-numeric"];
+        if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+          fileinto "inbox.JUNK";
+          stop;
+        }
 Test 8


 From CALLER@??? Tue Mar 02 09:44:33 1999
 Return-path: <CALLER@???>
-Envelope-to: userx@???
+Envelope-to: userx10@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbI-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
+    for userx10@???; Tue, 2 Mar 1999 09:44:33 +0000
 X-Sieve: 99-
-Filter: require["fileinto","comparator-i;ascii-numeric"];
-        if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
-          fileinto "inbox.JUNK";
-          stop;
-        }
 Message-Id: <E10HmbI-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require["fileinto","comparator-i;ascii-numeric"];
+        if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+          fileinto "inbox.JUNK";
+          stop;
+        }
 Test 10


 From CALLER@??? Tue Mar 02 09:44:33 1999
 Return-path: <CALLER@???>
-Envelope-to: userx@???
+Envelope-to: userx11@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbJ-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
+    for userx11@???; Tue, 2 Mar 1999 09:44:33 +0000
 X-Sieve: -99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
-        if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
-          fileinto "inbox.JUNK";
-          stop;
-        }
 Message-Id: <E10HmbJ-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require["fileinto","comparator-i;ascii-numeric"];
+        if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
+          fileinto "inbox.JUNK";
+          stop;
+        }
 Test 11


 From CALLER@??? Tue Mar 02 09:44:33 1999
 Return-path: <CALLER@???>
-Envelope-to: userx@???
+Envelope-to: userx12@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbK-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
+    for userx12@???; Tue, 2 Mar 1999 09:44:33 +0000
 X-Sieve: -99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
-        if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
-          fileinto "inbox.JUNK";
-          stop;
-        }
 Message-Id: <E10HmbK-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require["fileinto","comparator-i;ascii-numeric"];
+        if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
+          fileinto "inbox.JUNK";
+          stop;
+        }
 Test 12


diff --git a/test/mail/0428.someone b/test/mail/0428.someone
index a3ba52b..8939761 100644
--- a/test/mail/0428.someone
+++ b/test/mail/0428.someone
@@ -5,7 +5,7 @@ Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     id 10HmbM-0005vi-00
     for someone@???; Tue, 2 Mar 1999 09:44:33 +0000
-From: userx@???
+From: userx13@???
 To: someone@???
 Subject: Automated reply
 In-Reply-To: <E10HmbL-0005vi-00@???>
diff --git a/test/mail/0428.userx b/test/mail/0428.userx
index 8c3402b..b47f439 100644
--- a/test/mail/0428.userx
+++ b/test/mail/0428.userx
@@ -27,59 +27,3 @@ Date: Tue, 2 Mar 1999 09:44:33 +0000


Test 7

-From CALLER@??? Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@???>
-Envelope-to: userx@???
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
-    (envelope-from <CALLER@???>)
-    id 10HmbH-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: 99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
-        if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
-          fileinto "inbox.JUNK";
-          stop;
-        }
-Message-Id: <E10HmbH-0005vi-00@???>
-From: CALLER_NAME <CALLER@???>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 9
-
-From someone@??? Tue Mar 02 09:44:33 1999
-Return-path: <someone@???>
-Envelope-to: userx@???
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
-    (envelope-from <someone@???>)
-    id 10HmbL-0005vi-00
-    for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-To: userx@???
-Filter: require ["vacation"]; 
-        vacation "I am gone.  Not here.";
-Message-Id: <E10HmbL-0005vi-00@???>
-From: someone@???
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 13
-
-From someone@??? Tue Mar 02 09:44:33 1999
-Return-path: <someone@???>
-Envelope-to: userx-suffix2@???
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
-    (envelope-from <someone@???>)
-    id 10HmbN-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
-To: userx-suffix@???
-Filter: require ["envelope","fileinto"]; 
-        if envelope :matches :localpart "to" "*-suffix" {
-          fileinto "userx-sawsuffix";
-          stop; 
-        }   
-Message-Id: <E10HmbN-0005vi-00@???>
-From: someone@???
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 14
-
diff --git a/test/mail/0428.userx-sawsuffix b/test/mail/0428.userx-sawsuffix
index ef2e8b5..c2a457a 100644
--- a/test/mail/0428.userx-sawsuffix
+++ b/test/mail/0428.userx-sawsuffix
@@ -1,19 +1,18 @@
 From someone@??? Tue Mar 02 09:44:33 1999
 Return-path: <someone@???>
-Envelope-to: userx-suffix@???
+Envelope-to: userx14-suffix@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <someone@???>)
     id 10HmbN-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
-To: userx-suffix@???
-Filter: require ["envelope","fileinto"]; 
-        if envelope :matches :localpart "to" "*-suffix" {
-          fileinto "userx-sawsuffix";
-          stop; 
-        }   
 Message-Id: <E10HmbN-0005vi-00@???>
 From: someone@???
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require ["envelope","fileinto"]; 
+        if envelope :matches :localpart "to" "*-suffix" {
+          fileinto "userx-sawsuffix";
+          stop; 
+        }   
 Test 14


diff --git a/test/mail/0428.userx13 b/test/mail/0428.userx13
new file mode 100644
index 0000000..7a70d93
--- /dev/null
+++ b/test/mail/0428.userx13
@@ -0,0 +1,17 @@
+From someone@??? Tue Mar 02 09:44:33 1999
+Return-path: <someone@???>
+Envelope-to: userx13@???
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+    (envelope-from <someone@???>)
+    id 10HmbL-0005vi-00
+    for userx13@???; Tue, 2 Mar 1999 09:44:33 +0000
+To: userx13@???
+Message-Id: <E10HmbL-0005vi-00@???>
+From: someone@???
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+    require ["vacation"]; 
+        vacation "I am gone.  Not here.";
+Test 13
+
diff --git a/test/mail/0428.userx-sawsuffix b/test/mail/0428.userx14
similarity index 83%
copy from test/mail/0428.userx-sawsuffix
copy to test/mail/0428.userx14
index ef2e8b5..0c542a7 100644
--- a/test/mail/0428.userx-sawsuffix
+++ b/test/mail/0428.userx14
@@ -1,19 +1,18 @@
 From someone@??? Tue Mar 02 09:44:33 1999
 Return-path: <someone@???>
-Envelope-to: userx-suffix@???
+Envelope-to: userx14-suffix2@???
 Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
 Received: from CALLER by mail.test.ex with local (Exim x.yz)
     (envelope-from <someone@???>)
     id 10HmbN-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
-To: userx-suffix@???
-Filter: require ["envelope","fileinto"]; 
-        if envelope :matches :localpart "to" "*-suffix" {
-          fileinto "userx-sawsuffix";
-          stop; 
-        }   
 Message-Id: <E10HmbN-0005vi-00@???>
 From: someone@???
 Date: Tue, 2 Mar 1999 09:44:33 +0000


+    require ["envelope","fileinto"]; 
+        if envelope :matches :localpart "to" "*-suffix" {
+          fileinto "userx-sawsuffix";
+          stop; 
+        }   
 Test 14


diff --git a/test/mail/0428.userx9 b/test/mail/0428.userx9
new file mode 100644
index 0000000..1cfb156
--- /dev/null
+++ b/test/mail/0428.userx9
@@ -0,0 +1,20 @@
+From CALLER@??? Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@???>
+Envelope-to: userx9@???
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbH-0005vi-00
+    for userx9@???; Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: 99
+Message-Id: <E10HmbH-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+    require["fileinto","comparator-i;ascii-numeric"];
+        if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
+          fileinto "inbox.JUNK";
+          stop;
+        }
+Test 9
+
diff --git a/test/mail/5000.new/1.myhost.test.ex b/test/mail/5000.new/1.myhost.test.ex
index 5171213..efa7079 100644
--- a/test/mail/5000.new/1.myhost.test.ex
+++ b/test/mail/5000.new/1.myhost.test.ex
@@ -2,7 +2,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmaY-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_
 Message-Id: <E10HmaY-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/2.myhost.test.ex b/test/mail/5000.new/2.myhost.test.ex
index 1b5b064..e2afa21 100644
--- a/test/mail/5000.new/2.myhost.test.ex
+++ b/test/mail/5000.new/2.myhost.test.ex
@@ -2,7 +2,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmaZ-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_
 Message-Id: <E10HmaZ-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/3.myhost.test.ex:S370 b/test/mail/5000.new/3.myhost.test.ex:S370
index 7ea68ff..7225056 100644
--- a/test/mail/5000.new/3.myhost.test.ex:S370
+++ b/test/mail/5000.new/3.myhost.test.ex:S370
@@ -3,8 +3,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbB-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_tagged_
-tag:Ssss
 Message-Id: <E10HmbB-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/4.myhost.test.ex,S=370 b/test/mail/5000.new/4.myhost.test.ex,S=370
index ed38791..d7b4768 100644
--- a/test/mail/5000.new/4.myhost.test.ex,S=370
+++ b/test/mail/5000.new/4.myhost.test.ex,S=370
@@ -3,8 +3,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbC-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_tagged_
-tag:,S=sss
 Message-Id: <E10HmbC-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/5.myhost.test.ex b/test/mail/5000.new/5.myhost.test.ex
index 054bf90..41aedbc 100644
--- a/test/mail/5000.new/5.myhost.test.ex
+++ b/test/mail/5000.new/5.myhost.test.ex
@@ -3,8 +3,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbD-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_tagged_
-tag:${if eq{0}{1}{rhubarb}fail}
 Message-Id: <E10HmbD-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/6.myhost.test.ex b/test/mail/5000.new/6.myhost.test.ex
index df71e17..a7210d1 100644
--- a/test/mail/5000.new/6.myhost.test.ex
+++ b/test/mail/5000.new/6.myhost.test.ex
@@ -3,13 +3,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbE-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_tagged_
-tag:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 Message-Id: <E10HmbE-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S b/test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S
index be53589..f33b5f2 100644
--- a/test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S
+++ b/test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S
@@ -3,8 +3,6 @@ Received: from CALLER by myhost.test.ex with local (Exim x.yz)
     (envelope-from <CALLER@???>)
     id 10HmbF-0005vi-00
     for userx@???; Tue, 2 Mar 1999 09:44:33 +0000
-maildir:maildir_taggedX_
-tag:,S=sss:2,S
 Message-Id: <E10HmbF-0005vi-00@???>
 From: CALLER_NAME <CALLER@???>
 Date: Tue, 2 Mar 1999 09:44:33 +0000
diff --git a/test/msglog/5000.10HmaX-0005vi-00 b/test/msglog/5000.10HmaX-0005vi-00
index ca265a6..e50b21c 100644
--- a/test/msglog/5000.10HmaX-0005vi-00
+++ b/test/msglog/5000.10HmaX-0005vi-00
@@ -1,2 +1,2 @@
 1999-03-02 09:44:33 Received from CALLER@??? U=CALLER P=local S=sss
-1999-03-02 09:44:33 userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${expand:$h_tag:}" (maildir_tag for maildir_tagged_appendfile transport) failed: internal expansion of "${if eq{0}{1}{rhubarb}" failed: syntax error in "if" item - "fail" expected
+1999-03-02 09:44:33 userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${if eq{0}{1}{rhubarb}" (maildir_tag for maildir_tagged_appendfile transport) failed: syntax error in "if" item - "fail" expected
diff --git a/test/paniclog/1003 b/test/paniclog/1003
index 89086b0..d85ecc4 100644
--- a/test/paniclog/1003
+++ b/test/paniclog/1003
@@ -1 +1 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=client T=send_to_server defer (-1): failed to expand "interface" option for send_to_server transport: internal expansion of "<; ${if" failed: condition name expected, but found ""
+1999-03-02 09:44:33 10HmaX-0005vi-00 == user6@??? R=client T=send_to_server5 defer (-1): failed to expand "interface" option for send_to_server5 transport: condition name expected, but found ""
diff --git a/test/paniclog/5000 b/test/paniclog/5000
index 2427585..0782516 100644
--- a/test/paniclog/5000
+++ b/test/paniclog/5000
@@ -1 +1 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${expand:$h_tag:}" (maildir_tag for maildir_tagged_appendfile transport) failed: internal expansion of "${if eq{0}{1}{rhubarb}" failed: syntax error in "if" item - "fail" expected
+1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${if eq{0}{1}{rhubarb}" (maildir_tag for maildir_tagged_appendfile transport) failed: syntax error in "if" item - "fail" expected
diff --git a/test/runtest b/test/runtest
index fe1f4ef..a7b265b 100755
--- a/test/runtest
+++ b/test/runtest
@@ -772,7 +772,7 @@ RESET_AFTER_EXTRA_LINE_READ:
     }


# Port in host address in spool file output from -Mvh
- s/^-host_address (.*)\.\d+/-host_address $1.9999/;
+ s/^(--?host_address) (.*)\.\d+/$1 $2.9999/;

   if ($dynamic_socket and $dynamic_socket->opened and my $port = $dynamic_socket->sockport) {
     s/^Connecting to 127\.0\.0\.1 port \K$port/<dynamic port>/;
diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002
index 9334e04..8e654ac 100644
--- a/test/scripts/0000-Basic/0002
+++ b/test/scripts/0000-Basic/0002
@@ -932,8 +932,9 @@ exim -be -DPTBC=print_topbitchars
 escape: ${escape:B7?F2?}
 ****
 # Checkout expansion debugging
-exim -d-all+expand -be
+exim -d-all+expand -f sndr@dom -be
 primary_hostname: $primary_hostname
+sender_address: $sender_address
 match:  ${if match{abcd}{\N^([ab]+)(\w+)$\N}{$2$1}fail}
 match:  ${if match{wxyz}{\N^([ab]+)(\w+)$\N}{$2$1}fail}
 ${if eq {1}{1}{yes}{${lookup{xx}lsearch{/non/exist}}}}
diff --git a/test/scripts/0000-Basic/0137 b/test/scripts/0000-Basic/0137
index 5694194..78e04dd 100644
--- a/test/scripts/0000-Basic/0137
+++ b/test/scripts/0000-Basic/0137
@@ -1,29 +1,33 @@
 # mailbox locking
-exim -odi userx
+exim -odi -f t1@foo userx
 Subject: First
-Transport: local_delivery
+
+local_delivery
 First message.
 ****
 exim_lock -v test-mail/userx
-exim -odi userx
+exim -odi -f t1@foo userx
 Subject: Second
-Transport: local_delivery
+
+local_delivery
 Second message
 ****
 exim -qf
 ****
 exim_lock -v -fcntl test-mail/userx
-exim -odi userx
+exim -odi -f t2@foo userx
 Subject: Third
-Transport: local_delivery_fcntl
+
+local_delivery_fcntl
 Third message
 ****
 exim -qf
 ****
 exim_lock -v -fcntl test-mail/userx
-exim -odi userx
+exim -odi -f t3@foo userx
 Subject: Fourth
-Transport: local_delivery_fcntl_blocking
+
+local_delivery_fcntl_blocking
 Fourth message
 ****
 exim -qf
diff --git a/test/scripts/0000-Basic/0428 b/test/scripts/0000-Basic/0428
index d554c80..24c1b12 100644
--- a/test/scripts/0000-Basic/0428
+++ b/test/scripts/0000-Basic/0428
@@ -29,9 +29,10 @@ Filter: if true { stop; fileinto "inbox.never"; }
 Test 7
 ****
 # This should fileinto inbox.JUNK (99 equal 99):
-exim -odi userx
+exim -odi userx8
 X-Sieve: 99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
+
+    require["fileinto","comparator-i;ascii-numeric"];
         if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
           fileinto "inbox.JUNK";
           stop;
@@ -39,9 +40,10 @@ Filter: require["fileinto","comparator-i;ascii-numeric"];
 Test 8
 ****
 # This should not fileinto inbox.JUNK (98 not equal 99):
-exim -odi userx
+exim -odi userx9
 X-Sieve: 99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
+
+    require["fileinto","comparator-i;ascii-numeric"];
         if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
           fileinto "inbox.JUNK";
           stop;
@@ -49,9 +51,10 @@ Filter: require["fileinto","comparator-i;ascii-numeric"];
 Test 9
 ****
 # This should fileinto inbox.JUNK (99-suffix equal 99):
-exim -odi userx
+exim -odi userx10
 X-Sieve: 99-
-Filter: require["fileinto","comparator-i;ascii-numeric"];
+
+    require["fileinto","comparator-i;ascii-numeric"];
         if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
           fileinto "inbox.JUNK";
           stop;
@@ -59,9 +62,10 @@ Filter: require["fileinto","comparator-i;ascii-numeric"];
 Test 10
 ****
 # This should fileinto inbox.JUNK (non-numeric equal non-numeric):
-exim -odi userx
+exim -odi userx11
 X-Sieve: -99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
+
+    require["fileinto","comparator-i;ascii-numeric"];
         if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
           fileinto "inbox.JUNK";
           stop;
@@ -69,9 +73,10 @@ Filter: require["fileinto","comparator-i;ascii-numeric"];
 Test 11
 ****
 # This should fileinto inbox.JUNK (non-numeric equal non-numeric):
-exim -odi userx
+exim -odi userx12
 X-Sieve: -99
-Filter: require["fileinto","comparator-i;ascii-numeric"];
+
+    require["fileinto","comparator-i;ascii-numeric"];
         if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
           fileinto "inbox.JUNK";
           stop;
@@ -79,16 +84,17 @@ Filter: require["fileinto","comparator-i;ascii-numeric"];
 Test 12
 ****
 # This is a simple test of "vacation"
-exim -odi -f someone@??? userx 
-To: userx@???
-Filter: require ["vacation"]; 
+exim -odi -f someone@??? userx13
+To: userx13@???
+
+    require ["vacation"]; 
         vacation "I am gone.  Not here.";
 Test 13
 ****
 # Test use of suffix
-exim -odi -f someone@??? userx-suffix userx-suffix2
-To: userx-suffix@???
-Filter: require ["envelope","fileinto"]; 
+exim -odi -f someone@??? userx14-suffix userx14-suffix2
+
+    require ["envelope","fileinto"]; 
         if envelope :matches :localpart "to" "*-suffix" {
           fileinto "userx-sawsuffix";
           stop; 
diff --git a/test/scripts/0000-Basic/0504 b/test/scripts/0000-Basic/0504
index a48e8b4..04f1959 100644
--- a/test/scripts/0000-Basic/0504
+++ b/test/scripts/0000-Basic/0504
@@ -1,12 +1,10 @@
 # transport filter command fails to execute
 need_ipv4
 #
-exim -odi userx
-transport: t1
+exim -DOPT=t1 -odi userx
 Test 1
 ****
-exim -odi userx
-transport: t2
+exim -DOPT=t2 -odi userx
 Test 2
 ****
 server PORT_S
@@ -21,8 +19,7 @@ RCPT TO
 DATA
 354 Send it
 ****
-exim -odi userx
-transport: t3
+exim -DOPT=t3 -odi userx
 Test 3
 ****
 no_msglog_check
diff --git a/test/scripts/1000-Basic-ipv6/1003 b/test/scripts/1000-Basic-ipv6/1003
index 646b1b4..1be6923 100644
--- a/test/scripts/1000-Basic-ipv6/1003
+++ b/test/scripts/1000-Basic-ipv6/1003
@@ -4,24 +4,29 @@ need_ipv6
 #
 exim -DSERVER=server -bd -oX PORT_D
 ****
-exim userx@???
-hosts: 127.0.0.1
+exim user4@???
+
+1
 interface: <; ::1 ; HOSTIPV4
 ****
-exim userx@???
-hosts: <; ::1
+exim user6@???
+
+2
 interface: <; HOSTIPV6 ; HOSTIPV4
 ****
-exim userx@???
-hosts: <; ::1
+exim user6@???
+
+3
 interface: <; ${if eq{0}{1}{HOSTIPV6}fail}
 ****
-exim userx@???
-hosts: <; ::1
+exim user6@???
+
+4
 interface: <; ${if eq{0}{1}{HOSTIPV6}{  }}
 ****
-exim userx@???
-hosts: <; ::1
+exim user6@???
+
+5
 interface: <; ${if 
 ****
 exim -qf
diff --git a/test/scripts/5000-maildir/5000 b/test/scripts/5000-maildir/5000
index 5b763fc..a1d1ce3 100644
--- a/test/scripts/5000-maildir/5000
+++ b/test/scripts/5000-maildir/5000
@@ -1,58 +1,37 @@
 # exim quota + warn threshold with maildir
-exim -odi userx
-maildir:maildir_
+exim -DVALUE=maildir_appendfile -odi userx
 This is a test message
 ****
 write test-data 100x60
-maildir:maildir_
 ++++
 ****
-exim -odi userx <test-data
+exim -DVALUE=maildir_appendfile -odi userx <test-data
 ****
-exim -odi userx <test-data
+exim -DVALUE=maildir_appendfile -odi userx <test-data
 ****
-exim -odi userx
-maildir:maildir_tagged_
-tag:S370
+exim -DVALUE=maildir_tagged_appendfile -DOPT=S370 -odi userx
 This is a test message
 ****
 sleep 1
-exim -odi userx
-maildir:maildir_tagged_
-tag:,S=370
+exim -DVALUE=maildir_tagged_appendfile -DOPT=,S=370 -odi userx
 This is a test message
 ****
 sleep 1
-exim -odi userx
-maildir:maildir_tagged_
-tag:${if eq{0}{1}{rhubarb}fail}
+exim -DVALUE=maildir_tagged_appendfile -DOPT='${if eq{0}{1}{rhubarb}fail}' -odi userx
 This is a test message
 ****
 sleep 1
 # deliberate syntax fail
-exim -odi userx
-maildir:maildir_tagged_
-tag:${if eq{0}{1}{rhubarb}
+exim -DVALUE=maildir_tagged_appendfile -DOPT='${if eq{0}{1}{rhubarb}' -odi userx
 This is a test message
 ****
 sleep 1
 # overlongname
-exim -odi userx
-maildir:maildir_tagged_
-tag:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+exim -DVALUE=maildir_tagged_appendfile -DOPT=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -odi userx
 This is a test message
 ****
 sleep 1
-exim -odi userx
-maildir:maildir_taggedX_
-tag:,S=10694953:2,S
+exim -DVALUE=maildir_taggedX_appendfile -DOPT=,S=10694953:2,S -odi userx
 ****
-exim -d-all+transport -odi userx
-maildir:maildir_taggedX_
-tag:,S=412:2,S
+exim -d-all+transport -DVALUE=maildir_taggedX_appendfile -DOPT=,S=412:2,S -odi userx
 ****
diff --git a/test/scripts/5100-lmtp-transport/5103 b/test/scripts/5100-lmtp-transport/5103
index 3d7ace5..ce89440 100644
--- a/test/scripts/5100-lmtp-transport/5103
+++ b/test/scripts/5100-lmtp-transport/5103
@@ -2,7 +2,6 @@
 need_ipv4
 #
 exim -odi userx
-transport: t1
 Test 1
 ****
 no_msglog_check
diff --git a/test/stderr/0002 b/test/stderr/0002
index 41bf884..8f85e71 100644
--- a/test/stderr/0002
+++ b/test/stderr/0002
@@ -5,6 +5,10 @@ dropping to exim gid; retaining priv uid
  ╭considering: primary_hostname: $primary_hostname
  ├──expanding: primary_hostname: $primary_hostname
  ╰─────result: primary_hostname: myhost.test.ex
+ ╭considering: sender_address: $sender_address
+ ├──expanding: sender_address: $sender_address
+ ╰─────result: sender_address: sndr@dom
+            ╰──(tainted)
  ╭considering: match:  ${if match{abcd}{\N^([ab]+)(\w+)$\N}{$2$1}fail}
   ╭considering: abcd}{\N^([ab]+)(\w+)$\N}{$2$1}fail}
   ├──expanding: abcd
@@ -79,7 +83,7 @@ LOG: MAIN PANIC
   ╭considering: no}}
   ├──expanding: no
   ╰─────result: no
- ├──expanding: a.b.c
+ ├──expanding: match_address:   ${if match_address{a.b.c}{a.b.c}{yes}{no}}
  ╰─────result: match_address:   no

>>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>

Exim version x.yz ....
@@ -163,7 +167,7 @@ LOG: MAIN PANIC
/considering: no}}
|--expanding: no
\_____result: no
- |--expanding: a.b.c
+ |--expanding: match_address: ${if match_address{a.b.c}{a.b.c}{yes}{no}}
\_____result: match_address: no
>>>>>>>>>>>>>>>> Exim pid=pppp (main: expansion test) terminating with rc=0 >>>>>>>>>>>>>>>>

 Exim version x.yz ....
@@ -182,9 +186,11 @@ dropping to exim gid; retaining priv uid
  ╭considering: -oMai authenticated_id = $authenticated_id
  ├──expanding: -oMai authenticated_id = $authenticated_id
  ╰─────result: -oMai authenticated_id = philip
+            ╰──(tainted)
  ╭considering: -oMas authenticated_sender = $authenticated_sender
  ├──expanding: -oMas authenticated_sender = $authenticated_sender
  ╰─────result: -oMas authenticated_sender = xx@???
+            ╰──(tainted)
  ╭considering: -oMi  interface_address = $interface_address
  ├──expanding: -oMi  interface_address = $interface_address
  ╰─────result: -oMi  interface_address = 1.1.1.1
@@ -215,9 +221,11 @@ dropping to exim gid; retaining priv uid
  ╭considering: -oMai authenticated_id = $authenticated_id
  ├──expanding: -oMai authenticated_id = $authenticated_id
  ╰─────result: -oMai authenticated_id = philip
+            ╰──(tainted)
  ╭considering: -oMas authenticated_sender = $authenticated_sender
  ├──expanding: -oMas authenticated_sender = $authenticated_sender
  ╰─────result: -oMas authenticated_sender = xx@???
+            ╰──(tainted)
  ╭considering: -oMi  interface_address = $interface_address
  ├──expanding: -oMi  interface_address = $interface_address
  ╰─────result: -oMi  interface_address = 1.1.1.1
@@ -255,6 +263,7 @@ sender_fullhost = ten-1.test.ex [V4NET.0.0.1]
 sender_rcvhost = ten-1.test.ex ([V4NET.0.0.1] ident=me)
  ├──expanding: -oMs  sender_host_name = $sender_host_name
  ╰─────result: -oMs  sender_host_name = ten-1.test.ex
+            ╰──(tainted)
  ╭considering: -oMt  sender_ident = $sender_ident
  ├──expanding: -oMt  sender_ident = $sender_ident
  ╰─────result: -oMt  sender_ident = me
diff --git a/test/stderr/0023 b/test/stderr/0023
index b758875..cb371d2 100644
--- a/test/stderr/0023
+++ b/test/stderr/0023
@@ -1247,7 +1247,7 @@ LOG: H=[30.30.30.30] F=<a@???> rejected RCPT <x@y>: domain=test.e

 >>> check dnslists = test.ex/$sender_address_domain+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+END
 >>>                = test.ex/y+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+END
 >>> DNS list check: test.ex/y+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+END

-LOG: dnslist query is too long (ignored): y+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+...
+LOG: dnslist query is too long (ignored): y+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+extra+e...
>>> deny: condition test failed in ACL "acl_31_31_31"
>>> processing "accept" (TESTSUITE/test-config 168)
>>> accept: condition test succeeded in ACL "acl_31_31_31"

diff --git a/test/stderr/0037 b/test/stderr/0037
index af8bf12..5136a8f 100644
--- a/test/stderr/0037
+++ b/test/stderr/0037
@@ -9,8 +9,8 @@ configuration file is TESTSUITE/test-config
 trusted user
 admin user
 dropping to exim gid; retaining priv uid
-rda_interpret (file): TESTSUITE/aux-var/0037.F
-expanded: TESTSUITE/aux-var/0037.F
+rda_interpret (file): 'TESTSUITE/aux-var/0037.F'
+expanded: 'TESTSUITE/aux-var/0037.F'
 ssss bytes read from TESTSUITE/aux-var/0037.F
 data is an Exim filter program
 Filter: start of processing
@@ -39,8 +39,8 @@ local_part=filter-userx domain=test.ex
 checking local_parts
 $home = >/usr<
 calling userfilter router
-rda_interpret (file): TESTSUITE/aux-var/0037.f-user
-expanded: TESTSUITE/aux-var/0037.f-user
+rda_interpret (file): 'TESTSUITE/aux-var/0037.f-user'
+expanded: 'TESTSUITE/aux-var/0037.f-user'
 ssss bytes read from TESTSUITE/aux-var/0037.f-user
 data is an Exim filter program
 Filter: start of processing
diff --git a/test/stderr/0084 b/test/stderr/0084
index 4100ebe..45c7bd0 100644
--- a/test/stderr/0084
+++ b/test/stderr/0084
@@ -17,8 +17,8 @@ file check: TESTSUITE/aux-fixed/0084.$local_part
 expanded file: TESTSUITE/aux-fixed/0084.yes
 stat() yielded 0
 calling forward router
-rda_interpret (file): TESTSUITE/aux-fixed/0084.$local_part
-expanded: TESTSUITE/aux-fixed/0084.yes
+rda_interpret (file): 'TESTSUITE/aux-fixed/0084.$local_part'
+expanded: 'TESTSUITE/aux-fixed/0084.yes'
 ssss bytes read from TESTSUITE/aux-fixed/0084.yes
 file is not a filter file
 parse_forward_list: userx@???
diff --git a/test/stderr/0085 b/test/stderr/0085
index 86ef584..56c65f6 100644
--- a/test/stderr/0085
+++ b/test/stderr/0085
@@ -27,8 +27,8 @@ checking domains
 y.z in "test.ex : myhost.test.ex"? no (end of list)
 y.z in "! +local_domains"? yes (end of list)
 calling fail_remote_domains router
-rda_interpret (string): :fail: unrouteable mail domain "$domain"
-expanded: :fail: unrouteable mail domain "y.z"
+rda_interpret (string): ':fail: unrouteable mail domain "$domain"'
+expanded: ':fail: unrouteable mail domain "y.z"'
 file is not a filter file
 parse_forward_list: :fail: unrouteable mail domain "y.z"
 extract item: :fail: unrouteable mail domain "y.z"
@@ -226,8 +226,8 @@ checking domains
 y.z in "test.ex : myhost.test.ex"? no (end of list)
 y.z in "! +local_domains"? yes (end of list)
 calling fail_remote_domains router
-rda_interpret (string): :fail: unrouteable mail domain "$domain"
-expanded: :fail: unrouteable mail domain "y.z"
+rda_interpret (string): ':fail: unrouteable mail domain "$domain"'
+expanded: ':fail: unrouteable mail domain "y.z"'
 file is not a filter file
 parse_forward_list: :fail: unrouteable mail domain "y.z"
 extract item: :fail: unrouteable mail domain "y.z"
@@ -281,8 +281,8 @@ checking domains
 smart.domain in "test.ex : myhost.test.ex"? no (end of list)
 smart.domain in "! +local_domains"? yes (end of list)
 calling fail_remote_domains router
-rda_interpret (string): :fail: unrouteable mail domain "$domain"
-expanded: :fail: unrouteable mail domain "smart.domain"
+rda_interpret (string): ':fail: unrouteable mail domain "$domain"'
+expanded: ':fail: unrouteable mail domain "smart.domain"'
 file is not a filter file
 parse_forward_list: :fail: unrouteable mail domain "smart.domain"
 extract item: :fail: unrouteable mail domain "smart.domain"
diff --git a/test/stderr/0123 b/test/stderr/0123
index 34c2c1e..93894e4 100644
--- a/test/stderr/0123
+++ b/test/stderr/0123
@@ -16,7 +16,7 @@ routing x@???
 --------> l1 router <--------
 local_part=x domain=test.ex
 calling l1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases1"
  search_find: file="TESTSUITE/aux-fixed/0123.aliases1"
    key="x" partial=-1 affix=NULL starflags=0
@@ -28,14 +28,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases1
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l1 router declined for x@???
 --------> l2 router <--------
 local_part=x domain=test.ex
 calling l2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases2"
  search_find: file="TESTSUITE/aux-fixed/0123.aliases2"
    key="x" partial=-1 affix=NULL starflags=0
@@ -48,14 +48,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases2
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l2 router declined for x@???
 --------> l3 router <--------
 local_part=x domain=test.ex
 calling l3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases3"
  Too many lookup files open
    closing 0TESTSUITE/aux-fixed/0123.aliases1
@@ -70,14 +70,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases3
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l3 router declined for x@???
 --------> c1 router <--------
 local_part=x domain=test.ex
 calling c1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases4"
  Too many lookup files open
    closing 0TESTSUITE/aux-fixed/0123.aliases2
@@ -92,14 +92,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases4
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c1 router declined for x@???
 --------> c2 router <--------
 local_part=x domain=test.ex
 calling c2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases5"
  Too many lookup files open
    closing 0TESTSUITE/aux-fixed/0123.aliases3
@@ -114,14 +114,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases5
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c2 router declined for x@???
 --------> c3 router <--------
 local_part=x domain=test.ex
 calling c3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases6"
  Too many lookup files open
    closing 0TESTSUITE/aux-fixed/0123.aliases4
@@ -136,7 +136,7 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for x
    in TESTSUITE/aux-fixed/0123.aliases6
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c3 router declined for x@???
@@ -150,7 +150,7 @@ routing y@???
 --------> l1 router <--------
 local_part=y domain=test.ex
 calling l1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases1"
    cached closed
  Too many lookup files open
@@ -166,14 +166,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases1
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l1 router declined for y@???
 --------> l2 router <--------
 local_part=y domain=test.ex
 calling l2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases2"
    cached closed
  Too many lookup files open
@@ -189,14 +189,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases2
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l2 router declined for y@???
 --------> l3 router <--------
 local_part=y domain=test.ex
 calling l3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases3"
    cached closed
  Too many lookup files open
@@ -212,14 +212,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases3
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l3 router declined for y@???
 --------> c1 router <--------
 local_part=y domain=test.ex
 calling c1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases4"
    cached closed
  Too many lookup files open
@@ -235,14 +235,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases4
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c1 router declined for y@???
 --------> c2 router <--------
 local_part=y domain=test.ex
 calling c2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases5"
    cached closed
  Too many lookup files open
@@ -258,14 +258,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases5
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c2 router declined for y@???
 --------> c3 router <--------
 local_part=y domain=test.ex
 calling c3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases6"
    cached closed
  Too many lookup files open
@@ -281,7 +281,7 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for y
    in TESTSUITE/aux-fixed/0123.aliases6
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c3 router declined for y@???
@@ -295,7 +295,7 @@ routing z@???
 --------> l1 router <--------
 local_part=z domain=test.ex
 calling l1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases1}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases1"
    cached closed
  Too many lookup files open
@@ -311,14 +311,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases1
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l1 router declined for z@???
 --------> l2 router <--------
 local_part=z domain=test.ex
 calling l2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases2}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases2"
    cached closed
  Too many lookup files open
@@ -334,14 +334,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases2
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l2 router declined for z@???
 --------> l3 router <--------
 local_part=z domain=test.ex
 calling l3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases3}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases3"
    cached closed
  Too many lookup files open
@@ -357,14 +357,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases3
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 l3 router declined for z@???
 --------> c1 router <--------
 local_part=z domain=test.ex
 calling c1 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases4}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases4"
    cached closed
  Too many lookup files open
@@ -380,14 +380,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases4
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c1 router declined for z@???
 --------> c2 router <--------
 local_part=z domain=test.ex
 calling c2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases5}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases5"
    cached closed
  Too many lookup files open
@@ -403,14 +403,14 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases5
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c2 router declined for z@???
 --------> c3 router <--------
 local_part=z domain=test.ex
 calling c3 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.aliases6}}'
  search_open: lsearch "TESTSUITE/aux-fixed/0123.aliases6"
    cached closed
  Too many lookup files open
@@ -426,7 +426,7 @@ rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0123.al
  file lookup required for z
    in TESTSUITE/aux-fixed/0123.aliases6
  lookup failed
-expanded: 
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 c3 router declined for z@???
diff --git a/test/stderr/0279 b/test/stderr/0279
index f69c13e..2052244 100644
--- a/test/stderr/0279
+++ b/test/stderr/0279
@@ -99,8 +99,8 @@ fun.1 in "fun.1 : fun.2"? yes (matched "fun.1")
 fun.1 in "+funny_domains"? yes (matched "+funny_domains")
 user1@fun.1 in "user1@+funny_domains"? yes (matched "user1@+funny_domains")
 calling rr1 router
-rda_interpret (string): :fail: matched *@+funny_domains
-expanded: :fail: matched *@+funny_domains
+rda_interpret (string): ':fail: matched *@+funny_domains'
+expanded: ':fail: matched *@+funny_domains'
 file is not a filter file
 parse_forward_list: :fail: matched *@+funny_domains
 extract item: :fail: matched *@+funny_domains
diff --git a/test/stderr/0297 b/test/stderr/0297
index 4706f87..36bcfb4 100644
--- a/test/stderr/0297
+++ b/test/stderr/0297
@@ -8,8 +8,8 @@ routing /a/b/c@???
 --------> r1 router <--------
 local_part=/a/b/c domain=myhost.test.ex
 calling r1 router
-rda_interpret (string): $local_part
-expanded: /a/b/c
+rda_interpret (string): '$local_part'
+expanded: '/a/b/c'
 file is not a filter file
 parse_forward_list: /a/b/c
 extract item: /a/b/c
@@ -32,8 +32,8 @@ routing /x/y/z@???
 --------> r1 router <--------
 local_part=/x/y/z domain=myhost.test.ex
 calling r1 router
-rda_interpret (string): $local_part
-expanded: /x/y/z
+rda_interpret (string): '$local_part'
+expanded: '/x/y/z'
 file is not a filter file
 parse_forward_list: /x/y/z
 extract item: /x/y/z
diff --git a/test/stderr/0360 b/test/stderr/0360
index e2ad7d4..de4b4f2 100644
--- a/test/stderr/0360
+++ b/test/stderr/0360
@@ -28,8 +28,8 @@ r1 router skipped: domains mismatch
 --------> r2 router <--------
 local_part=cms domain=test.ex
 calling r2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}
-expanded: unknown@???, defer
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}'
+expanded: 'unknown@???, defer'
 file is not a filter file
 parse_forward_list: unknown@???, defer
 extract item: unknown@???
@@ -83,8 +83,8 @@ r1 router skipped: domains mismatch
 --------> r2 router <--------
 local_part=defer domain=test.ex
 calling r2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}
-expanded: :defer: Forcibly deferred
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}'
+expanded: ':defer: Forcibly deferred'
 file is not a filter file
 parse_forward_list: :defer: Forcibly deferred
 extract item: :defer: Forcibly deferred
@@ -171,8 +171,8 @@ r1 router skipped: domains mismatch
 --------> r2 router <--------
 local_part=cms domain=test.ex
 calling r2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}
-expanded: unknown@???, defer
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}'
+expanded: 'unknown@???, defer'
 file is not a filter file
 parse_forward_list: unknown@???, defer
 extract item: unknown@???
@@ -226,8 +226,8 @@ r1 router skipped: domains mismatch
 --------> r2 router <--------
 local_part=defer domain=test.ex
 calling r2 router
-rda_interpret (string): ${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}
-expanded: :defer: Forcibly deferred
+rda_interpret (string): '${lookup{$local_part}lsearch{TESTSUITE/aux-fixed/0360.aliases}}'
+expanded: ':defer: Forcibly deferred'
 file is not a filter file
 parse_forward_list: :defer: Forcibly deferred
 extract item: :defer: Forcibly deferred
diff --git a/test/stderr/0361 b/test/stderr/0361
index 64e47df..9b74af4 100644
--- a/test/stderr/0361
+++ b/test/stderr/0361
@@ -141,8 +141,8 @@ local_part=kilos domain=recurse.test.ex.test.ex
 checking local_parts
 kilos in "kilos"? yes (matched "kilos")
 calling r3 router
-rda_interpret (string): $local_part@$domain
-expanded: kilos@???
+rda_interpret (string): '$local_part@$domain'
+expanded: 'kilos@???'
 file is not a filter file
 parse_forward_list: kilos@???
 extract item: kilos@???
diff --git a/test/stderr/0364 b/test/stderr/0364
index 81fb83d..1b08f72 100644
--- a/test/stderr/0364
+++ b/test/stderr/0364
@@ -9,8 +9,8 @@ local_part=kilos domain=thishost
 checking domains
 processing address_data
 calling r1 router
-rda_interpret (string): $local_part@$domain.test.ex
-expanded: kilos@???
+rda_interpret (string): '$local_part@$domain.test.ex'
+expanded: 'kilos@???'
 file is not a filter file
 parse_forward_list: kilos@???
 extract item: kilos@???
@@ -54,8 +54,8 @@ local_part=solik domain=otherhost
 checking domains
 processing address_data
 calling r1 router
-rda_interpret (string): $local_part@$domain.test.ex
-expanded: solik@???
+rda_interpret (string): '$local_part@$domain.test.ex'
+expanded: 'solik@???'
 file is not a filter file
 parse_forward_list: solik@???
 extract item: solik@???
@@ -80,8 +80,8 @@ checking domains
 checking "condition" "${if eq{$address_data}{}{no}{yes}}"...
 processing address_data
 calling r3 router
-rda_interpret (string): $local_part@$original_domain.sub.test.ex
-expanded: solik@???
+rda_interpret (string): '$local_part@$original_domain.sub.test.ex'
+expanded: 'solik@???'
 file is not a filter file
 parse_forward_list: solik@???
 extract item: solik@???
@@ -109,8 +109,8 @@ r3 router skipped: condition failure
 local_part=solik domain=otherhost.sub.test.ex
 checking domains
 calling r4 router
-rda_interpret (string): :fail:Can't route to $domain
-expanded: :fail:Can't route to otherhost.sub.test.ex
+rda_interpret (string): ':fail:Can't route to $domain'
+expanded: ':fail:Can't route to otherhost.sub.test.ex'
 file is not a filter file
 parse_forward_list: :fail:Can't route to otherhost.sub.test.ex
 extract item: :fail:Can't route to otherhost.sub.test.ex
@@ -122,8 +122,8 @@ local_part=xxx domain=ten-1
 checking domains
 processing address_data
 calling r1 router
-rda_interpret (string): $local_part@$domain.test.ex
-expanded: xxx@???
+rda_interpret (string): '$local_part@$domain.test.ex'
+expanded: 'xxx@???'
 file is not a filter file
 parse_forward_list: xxx@???
 extract item: xxx@???
@@ -157,8 +157,8 @@ local_part=xxx domain=testsub
 checking domains
 processing address_data
 calling r1 router
-rda_interpret (string): $local_part@$domain.test.ex
-expanded: xxx@???
+rda_interpret (string): '$local_part@$domain.test.ex'
+expanded: 'xxx@???'
 file is not a filter file
 parse_forward_list: xxx@???
 extract item: xxx@???
@@ -183,8 +183,8 @@ checking domains
 checking "condition" "${if eq{$address_data}{}{no}{yes}}"...
 processing address_data
 calling r3 router
-rda_interpret (string): $local_part@$original_domain.sub.test.ex
-expanded: xxx@???
+rda_interpret (string): '$local_part@$original_domain.sub.test.ex'
+expanded: 'xxx@???'
 file is not a filter file
 parse_forward_list: xxx@???
 extract item: xxx@???
diff --git a/test/stderr/0370 b/test/stderr/0370
index 823ff09..b2483cf 100644
--- a/test/stderr/0370
+++ b/test/stderr/0370
@@ -21,7 +21,7 @@ changed uid/gid: local delivery to |TESTSUITE/bin/iefbr14 <|TESTSUITE/bin/iefbr1
   uid=EXIM_UID gid=CALLER_GID pid=pppp
 t1 transport entered
 direct command:
-  argv[0] = TESTSUITE/bin/iefbr14
+  argv[0] = 'TESTSUITE/bin/iefbr14'
 Writing message to pipe
 writing data block fd=dddd size=sss timeout=3600
 writing error 32: Broken pipe
diff --git a/test/stderr/0377 b/test/stderr/0377
index 007df5d..98f92e0 100644
--- a/test/stderr/0377
+++ b/test/stderr/0377
@@ -54,8 +54,8 @@ cccc_2nd_time router skipped: condition failure
 local_part=cccc domain=myhost.test.ex
 checking local_parts
 calling cccc_redirect router
-rda_interpret (string): cccc@$domain, defer_cccc@$domain
-expanded: cccc@???, defer_cccc@???
+rda_interpret (string): 'cccc@$domain, defer_cccc@$domain'
+expanded: 'cccc@???, defer_cccc@???'
 file is not a filter file
 parse_forward_list: cccc@???, defer_cccc@???
 extract item: cccc@???
@@ -88,8 +88,8 @@ local_part=bbbb domain=myhost.test.ex
 checking local_parts
 checking "condition" "${if first_delivery{yes}{no}}"...
 calling bbbb router
-rda_interpret (string): bbbb@$domain, defer_bbbb@$domain
-expanded: bbbb@???, defer_bbbb@???
+rda_interpret (string): 'bbbb@$domain, defer_bbbb@$domain'
+expanded: 'bbbb@???, defer_bbbb@???'
 file is not a filter file
 parse_forward_list: bbbb@???, defer_bbbb@???
 extract item: bbbb@???
@@ -114,8 +114,8 @@ local_part=aaaa domain=myhost.test.ex
 checking local_parts
 checking "condition" "${if first_delivery{yes}{no}}"...
 calling unseen_aaaa router
-rda_interpret (string): defer_aaaa@$domain
-expanded: defer_aaaa@???
+rda_interpret (string): 'defer_aaaa@$domain'
+expanded: 'defer_aaaa@???'
 file is not a filter file
 parse_forward_list: defer_aaaa@???
 extract item: defer_aaaa@???
@@ -158,8 +158,8 @@ routing defer_cccc@???
 local_part=defer_cccc domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -216,8 +216,8 @@ routing defer_bbbb@???
 local_part=defer_bbbb domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -261,8 +261,8 @@ routing defer_aaaa@???
 local_part=defer_aaaa domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -356,8 +356,8 @@ local_part=cccc domain=myhost.test.ex
 checking local_parts
 checking "condition" "${if first_delivery{no}{yes}}"...
 calling cccc_2nd_time router
-rda_interpret (string): $local_part@$domain
-expanded: cccc@???
+rda_interpret (string): '$local_part@$domain'
+expanded: 'cccc@???'
 file is not a filter file
 parse_forward_list: cccc@???
 extract item: cccc@???
@@ -458,8 +458,8 @@ cccc_2nd_time router skipped: previously routed cccc@???
 local_part=cccc domain=myhost.test.ex
 checking local_parts
 calling cccc_redirect router
-rda_interpret (string): cccc@$domain, defer_cccc@$domain
-expanded: cccc@???, defer_cccc@???
+rda_interpret (string): 'cccc@$domain, defer_cccc@$domain'
+expanded: 'cccc@???, defer_cccc@???'
 file is not a filter file
 parse_forward_list: cccc@???, defer_cccc@???
 extract item: cccc@???
@@ -488,8 +488,8 @@ routing defer_cccc@???
 local_part=defer_cccc domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -600,8 +600,8 @@ local_part=cccc domain=myhost.test.ex
 checking local_parts
 checking "condition" "${if first_delivery{no}{yes}}"...
 calling cccc_2nd_time router
-rda_interpret (string): $local_part@$domain
-expanded: cccc@???
+rda_interpret (string): '$local_part@$domain'
+expanded: 'cccc@???'
 file is not a filter file
 parse_forward_list: cccc@???
 extract item: cccc@???
@@ -702,8 +702,8 @@ cccc_2nd_time router skipped: previously routed cccc@???
 local_part=cccc domain=myhost.test.ex
 checking local_parts
 calling cccc_redirect router
-rda_interpret (string): cccc@$domain, defer_cccc@$domain
-expanded: cccc@???, defer_cccc@???
+rda_interpret (string): 'cccc@$domain, defer_cccc@$domain'
+expanded: 'cccc@???, defer_cccc@???'
 file is not a filter file
 parse_forward_list: cccc@???, defer_cccc@???
 extract item: cccc@???
@@ -732,8 +732,8 @@ routing defer_cccc@???
 local_part=defer_cccc domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
diff --git a/test/stderr/0378 b/test/stderr/0378
index 74ab959..e48fb7e 100644
--- a/test/stderr/0378
+++ b/test/stderr/0378
@@ -34,18 +34,13 @@ aaaa_2nd_time router skipped: condition failure
 local_part=aaaa domain=myhost.test.ex
 checking local_parts
 calling aaaa router
-rda_interpret (string): # Exim filter
-deliver defer_aaaa@$domain
-save TESTSUITE/test-mail/file
-pipe "/bin/sh -c exit"
-mail subject autoreply
-text "This is an autoreply"
-expanded: # Exim filter
+rda_interpret (string): '# Exim filter\ndeliver defer_aaaa@$domain\nsave TESTSUITE/test-mail/file\npipe "/bin/sh -c exit"\nmail subject autoreply\ntext "This is an autoreply"'
+expanded: '# Exim filter
 deliver defer_aaaa@???
 save TESTSUITE/test-mail/file
 pipe "/bin/sh -c exit"
 mail subject autoreply
-text "This is an autoreply"
+text "This is an autoreply"'
 data is an Exim filter program
 Filter: start of processing
 Filter: end of processing
@@ -99,8 +94,8 @@ bounce router skipped: senders mismatch
 local_part=defer_aaaa domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -141,8 +136,8 @@ routing CALLER@???
 local_part=CALLER domain=myhost.test.ex
 checking senders
 calling bounce router
-rda_interpret (string): :blackhole:
-expanded: :blackhole:
+rda_interpret (string): ':blackhole:'
+expanded: ':blackhole:'
 file is not a filter file
 parse_forward_list: :blackhole:
 extract item: :blackhole:
@@ -197,8 +192,8 @@ local_part=aaaa domain=myhost.test.ex
 checking local_parts
 checking "condition" "${if first_delivery{no}{yes}}"...
 calling aaaa_2nd_time router
-rda_interpret (string): aaaa@$domain
-expanded: aaaa@???
+rda_interpret (string): 'aaaa@$domain'
+expanded: 'aaaa@???'
 file is not a filter file
 parse_forward_list: aaaa@???
 extract item: aaaa@???
@@ -229,18 +224,13 @@ aaaa_2nd_time router skipped: previously routed aaaa@???
 local_part=aaaa domain=myhost.test.ex
 checking local_parts
 calling aaaa router
-rda_interpret (string): # Exim filter
-deliver defer_aaaa@$domain
-save TESTSUITE/test-mail/file
-pipe "/bin/sh -c exit"
-mail subject autoreply
-text "This is an autoreply"
-expanded: # Exim filter
+rda_interpret (string): '# Exim filter\ndeliver defer_aaaa@$domain\nsave TESTSUITE/test-mail/file\npipe "/bin/sh -c exit"\nmail subject autoreply\ntext "This is an autoreply"'
+expanded: '# Exim filter
 deliver defer_aaaa@???
 save TESTSUITE/test-mail/file
 pipe "/bin/sh -c exit"
 mail subject autoreply
-text "This is an autoreply"
+text "This is an autoreply"'
 data is an Exim filter program
 Filter: start of processing
 Filter: end of processing
@@ -293,8 +283,8 @@ bounce router skipped: senders mismatch
 local_part=defer_aaaa domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
diff --git a/test/stderr/0379 b/test/stderr/0379
index c3777c2..26a225f 100644
--- a/test/stderr/0379
+++ b/test/stderr/0379
@@ -29,8 +29,8 @@ defer router skipped: local_parts mismatch
 local_part=aaaa domain=myhost.test.ex
 checking local_parts
 calling aaaa_redirect router
-rda_interpret (string): bbbb@$domain, cccc@$domain
-expanded: bbbb@???, cccc@???
+rda_interpret (string): 'bbbb@$domain, cccc@$domain'
+expanded: 'bbbb@???, cccc@???'
 file is not a filter file
 parse_forward_list: bbbb@???, cccc@???
 extract item: bbbb@???
@@ -50,8 +50,8 @@ routing defer@???
 local_part=defer domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
@@ -151,8 +151,8 @@ routing defer@???
 local_part=defer domain=myhost.test.ex
 checking local_parts
 calling defer router
-rda_interpret (string): :defer: forced defer
-expanded: :defer: forced defer
+rda_interpret (string): ':defer: forced defer'
+expanded: ':defer: forced defer'
 file is not a filter file
 parse_forward_list: :defer: forced defer
 extract item: :defer: forced defer
diff --git a/test/stderr/0380 b/test/stderr/0380
index 7be8b5b..c653fc3 100644
--- a/test/stderr/0380
+++ b/test/stderr/0380
@@ -33,8 +33,8 @@ r1 router skipped: local_parts mismatch
 local_part=bbbb domain=myhost.test.ex
 checking local_parts
 calling r2 router
-rda_interpret (file): TESTSUITE/non-exist/$local_part
-expanded: TESTSUITE/non-exist/bbbb
+rda_interpret (file): 'TESTSUITE/non-exist/$local_part'
+expanded: 'TESTSUITE/non-exist/bbbb'
 TESTSUITE/non-exist/bbbb does not exist
 ignore_enotdir set => skip checking parent directory
 r2 router declined for bbbb@???
@@ -61,8 +61,8 @@ forced failure in expansion of "${if eq {a}{b}{x}fail}" (address_data): decline
 local_part=aaaa domain=myhost.test.ex
 checking local_parts
 calling r1 router
-rda_interpret (file): TESTSUITE/non-exist/$local_part
-expanded: TESTSUITE/non-exist/aaaa
+rda_interpret (file): 'TESTSUITE/non-exist/$local_part'
+expanded: 'TESTSUITE/non-exist/aaaa'
 TESTSUITE/non-exist/aaaa does not exist
 checking parent directory
 stat(TESTSUITE/non-exist/.)=-1
diff --git a/test/stderr/0382 b/test/stderr/0382
index 792e5ce..24fd7b7 100644
--- a/test/stderr/0382
+++ b/test/stderr/0382
@@ -10,8 +10,8 @@ trusted user
 admin user
 dropping to exim gid; retaining priv uid
 running system filter
-Filtering did not set up a significant delivery.
-Normal delivery will occur.
+ Filtering did not set up a significant delivery.
+ Normal delivery will occur.
 system filter returned 1
 LOG: MAIN
   ** userx@??? R=r1: forced fail
@@ -27,8 +27,8 @@ trusted user
 admin user
 dropping to exim gid; retaining priv uid
 running system filter
-Filtering did not set up a significant delivery.
-Normal delivery will occur.
+ Filtering did not set up a significant delivery.
+ Normal delivery will occur.
 system filter returned 1
 LOG: MAIN
   ** CALLER@???: Unrouteable address
diff --git a/test/stderr/0386 b/test/stderr/0386
index b8f4b07..d2444f5 100644
--- a/test/stderr/0386
+++ b/test/stderr/0386
@@ -32,19 +32,24 @@ spool directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
 log directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100

SMTP>> 250 OK

SMTP<< rcpt to:<1@b>
-read ACL from file TESTSUITE/aux-fixed/0386.acl1
-processing "accept" (TESTSUITE/test-config 32)
-check hosts = :
+using ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 18)
+check local_parts = 1
+1 in "1"? yes (matched "1")
+check acl = TESTSUITE/aux-fixed/0386.acl1
+ read ACL from file TESTSUITE/aux-fixed/0386.acl1
+ processing "accept" (TESTSUITE/test-config 43)
+ check hosts = :
host in ":"? no (end of list)
-accept: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "deny" (TESTSUITE/test-config 32)
-check local_parts = ^.*[@%!/|]
+ accept: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "deny" (TESTSUITE/test-config 43)
+ check local_parts = ^.*[@%!/|]
1 in "^.*[@%!/|]"? no (end of list)
-deny: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "require" (TESTSUITE/test-config 32)
-l_message: Invalid sender
- message: Couldn't verify the sender
-check verify = sender/defer_ok
+ deny: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "require" (TESTSUITE/test-config 43)
+ l_message: Invalid sender
+ message: Couldn't verify the sender
+ check verify = sender/defer_ok
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Verifying x@y
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

@@ -64,13 +69,15 @@ domain = y
routed by r1 router
envelope to: x@y
transport: t1
------------ end verify ------------
-sender x@y verified ok
-require: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "deny" (TESTSUITE/test-config 32)
- message: No such user here
-deny: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
-end of ACL "TESTSUITE/aux-fixed/0386.acl1": DENY
+ ----------- end verify ------------
+ sender x@y verified ok
+ require: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "deny" (TESTSUITE/test-config 43)
+ message: No such user here
+ deny: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ end of ACL "TESTSUITE/aux-fixed/0386.acl1": DENY
+accept: condition test failed in ACL "chk_rcpt"
+accept: endpass encountered - denying access
SMTP>> 550 No such user here

LOG: MAIN REJECT
H=[V4NET.9.8.7] F=<x@y> rejected RCPT <1@b>: No such user here
@@ -81,19 +88,24 @@ spool directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
log directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
SMTP>> 250 OK

SMTP<< rcpt to:<1@b>
-using ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "accept" (TESTSUITE/test-config 32)
-check hosts = :
+using ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 18)
+check local_parts = 1
+1 in "1"? yes (matched "1")
+check acl = TESTSUITE/aux-fixed/0386.acl1
+ using ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "accept" (TESTSUITE/test-config 43)
+ check hosts = :
host in ":"? no (end of list)
-accept: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "deny" (TESTSUITE/test-config 32)
-check local_parts = ^.*[@%!/|]
+ accept: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "deny" (TESTSUITE/test-config 43)
+ check local_parts = ^.*[@%!/|]
1 in "^.*[@%!/|]"? no (end of list)
-deny: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "require" (TESTSUITE/test-config 32)
-l_message: Invalid sender
- message: Couldn't verify the sender
-check verify = sender/defer_ok
+ deny: condition test failed in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "require" (TESTSUITE/test-config 43)
+ l_message: Invalid sender
+ message: Couldn't verify the sender
+ check verify = sender/defer_ok
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Verifying x@y
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

@@ -112,13 +124,15 @@ domain = y
routed by r1 router
envelope to: x@y
transport: t1
------------ end verify ------------
-sender x@y verified ok
-require: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
-processing "deny" (TESTSUITE/test-config 32)
- message: No such user here
-deny: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
-end of ACL "TESTSUITE/aux-fixed/0386.acl1": DENY
+ ----------- end verify ------------
+ sender x@y verified ok
+ require: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ processing "deny" (TESTSUITE/test-config 43)
+ message: No such user here
+ deny: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl1"
+ end of ACL "TESTSUITE/aux-fixed/0386.acl1": DENY
+accept: condition test failed in ACL "chk_rcpt"
+accept: endpass encountered - denying access
SMTP>> 550 No such user here

LOG: MAIN REJECT
H=[V4NET.9.8.7] F=<x@y> rejected RCPT <1@b>: No such user here
@@ -164,11 +178,20 @@ spool directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
log directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
SMTP>> 250 OK

SMTP<< rcpt to:<2@b>
-read ACL from file TESTSUITE/aux-fixed/0386.acl2
-processing "warn" (TESTSUITE/test-config 32)
- message: X-Warning: $sender_host_address is listed at $dnslist_domain\nX-Warning: $dnslist_text
-l_message: found in $dnslist_domain: $dnslist_text
-check dnslists = rbl.test.ex
+using ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 18)
+check local_parts = 1
+2 in "1"? no (end of list)
+accept: condition test failed in ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 21)
+check local_parts = 2
+2 in "2"? yes (matched "2")
+check acl = TESTSUITE/aux-fixed/0386.acl2
+ read ACL from file TESTSUITE/aux-fixed/0386.acl2
+ processing "warn" (TESTSUITE/test-config 43)
+ message: X-Warning: $sender_host_address is listed at $dnslist_domain\nX-Warning: $dnslist_text
+ l_message: found in $dnslist_domain: $dnslist_text
+ check dnslists = rbl.test.ex
DNS list check: rbl.test.ex
new DNS lookup for 13.12.11.V4NET.rbl.test.ex
DNS lookup of 13.12.11.V4NET.rbl.test.ex (A) using fakens
@@ -177,13 +200,15 @@ DNS lookup for 13.12.11.V4NET.rbl.test.ex succeeded (yielding 127.0.0.2)
DNS lookup of 13.12.11.V4NET.rbl.test.ex (TXT) using fakens
DNS lookup of 13.12.11.V4NET.rbl.test.ex (TXT) succeeded
=> that means V4NET.11.12.13 is listed at rbl.test.ex
-warn: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
+ warn: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
LOG: MAIN
H=[V4NET.11.12.13] U=CALLER Warning: found in rbl.test.ex: This is a test blacklisting message
created log directory TESTSUITE/spool/log
-processing "accept" (TESTSUITE/test-config 32)
-accept: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
-end of ACL "TESTSUITE/aux-fixed/0386.acl2": ACCEPT
+ processing "accept" (TESTSUITE/test-config 43)
+ accept: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
+ end of ACL "TESTSUITE/aux-fixed/0386.acl2": ACCEPT
+accept: condition test succeeded in ACL "chk_rcpt"
+end of ACL "chk_rcpt": ACCEPT
SMTP>> 250 Accepted

DSN: orcpt: NULL flags: 0
SMTP<< data
@@ -351,21 +376,32 @@ spool directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
log directory space = nnnnnK inodes = nnnnn check_space = 10240K inodes = 100
SMTP>> 250 OK

SMTP<< rcpt to:<2@b>
-using ACL "TESTSUITE/aux-fixed/0386.acl2"
-processing "warn" (TESTSUITE/test-config 32)
- message: X-Warning: $sender_host_address is listed at $dnslist_domain\nX-Warning: $dnslist_text
-l_message: found in $dnslist_domain: $dnslist_text
-check dnslists = rbl.test.ex
+using ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 18)
+check local_parts = 1
+2 in "1"? no (end of list)
+accept: condition test failed in ACL "chk_rcpt"
+processing "accept" (TESTSUITE/test-config 21)
+check local_parts = 2
+2 in "2"? yes (matched "2")
+check acl = TESTSUITE/aux-fixed/0386.acl2
+ using ACL "TESTSUITE/aux-fixed/0386.acl2"
+ processing "warn" (TESTSUITE/test-config 43)
+ message: X-Warning: $sender_host_address is listed at $dnslist_domain\nX-Warning: $dnslist_text
+ l_message: found in $dnslist_domain: $dnslist_text
+ check dnslists = rbl.test.ex
DNS list check: rbl.test.ex
using result of previous DNS lookup
DNS lookup for 13.12.11.V4NET.rbl.test.ex succeeded (yielding 127.0.0.2)
=> that means V4NET.11.12.13 is listed at rbl.test.ex
-warn: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
+ warn: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
LOG: MAIN
H=[V4NET.11.12.13] U=CALLER Warning: found in rbl.test.ex: This is a test blacklisting message
-processing "accept" (TESTSUITE/test-config 32)
-accept: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
-end of ACL "TESTSUITE/aux-fixed/0386.acl2": ACCEPT
+ processing "accept" (TESTSUITE/test-config 43)
+ accept: condition test succeeded in ACL "TESTSUITE/aux-fixed/0386.acl2"
+ end of ACL "TESTSUITE/aux-fixed/0386.acl2": ACCEPT
+accept: condition test succeeded in ACL "chk_rcpt"
+end of ACL "chk_rcpt": ACCEPT
SMTP>> 250 Accepted

DSN: orcpt: NULL flags: 0
SMTP<< data
diff --git a/test/stderr/0388 b/test/stderr/0388
index a9be60f..e11ca71 100644
--- a/test/stderr/0388
+++ b/test/stderr/0388
@@ -258,8 +258,8 @@ checking senders
address match test: subject= pattern=
in ":"? yes (matched "")
calling r0 router
-rda_interpret (string): :blackhole:
-expanded: :blackhole:
+rda_interpret (string): ':blackhole:'
+expanded: ':blackhole:'
file is not a filter file
parse_forward_list: :blackhole:
extract item: :blackhole:
diff --git a/test/stderr/0393 b/test/stderr/0393
index 34d2078..81ffa5e 100644
--- a/test/stderr/0393
+++ b/test/stderr/0393
@@ -12,7 +12,7 @@ dropping to exim gid; retaining priv uid
>>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>

--------> userx@??? <--------
direct command:
- argv[0] = /bin/cat
+ argv[0] = '/bin/cat'
direct command after expansion:
argv[0] = /bin/cat
appendfile transport entered
@@ -97,7 +97,7 @@ dropping to exim gid; retaining priv uid
>>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>

 --------> userx@??? <--------
 direct command:
-  argv[0] = ${if={1}{1}{}{}}
+  argv[0] = '${if={1}{1}{}{}}'
 direct command after expansion:
   argv[0] = 
 appendfile transport entered
diff --git a/test/stderr/0399 b/test/stderr/0399
index 2dc7172..9a3b132 100644
--- a/test/stderr/0399
+++ b/test/stderr/0399
@@ -16,8 +16,8 @@ routing x@y
 --------> r1 router <--------
 local_part=x domain=y
 calling r1 router
-rda_interpret (string): 
-expanded: 
+rda_interpret (string): ''
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 r1 router declined for x@y
@@ -25,8 +25,8 @@ expansion of "more" forced failure
 --------> r2 router <--------
 local_part=x domain=y
 calling r2 router
-rda_interpret (string): 
-expanded: 
+rda_interpret (string): ''
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 r2 router declined for x@y
@@ -34,8 +34,8 @@ expansion of "more" yields "yes"
 --------> r3 router <--------
 local_part=x domain=y
 calling r3 router
-rda_interpret (string): 
-expanded: 
+rda_interpret (string): ''
+expanded: ''
 file is not a filter file
 parse_forward_list: 
 r3 router declined for x@y
diff --git a/test/stderr/0402 b/test/stderr/0402
index daa7b50..1d81d84 100644
--- a/test/stderr/0402
+++ b/test/stderr/0402
@@ -284,9 +284,10 @@ usery in "usery"? yes (matched "usery")
  ╭considering: /non-exist/$domain
  ├──expanding: /non-exist/$domain
  ╰─────result: /non-exist/test.ex
+            ╰──(tainted)
 calling r5 router
-rda_interpret (string): TESTSUITE/test-mail/junk
-expanded: TESTSUITE/test-mail/junk
+rda_interpret (string): 'TESTSUITE/test-mail/junk'
+expanded: 'TESTSUITE/test-mail/junk'
 file is not a filter file
 parse_forward_list: TESTSUITE/test-mail/junk
 extract item: TESTSUITE/test-mail/junk
@@ -323,9 +324,10 @@ CALLER in "CALLER"? yes (matched "CALLER")
  ╭considering: /non-exist/$local_part
  ├──expanding: /non-exist/$local_part
  ╰─────result: /non-exist/CALLER
+            ╰──(tainted)
 calling r4 router
-rda_interpret (string): TESTSUITE/test-mail/junk
-expanded: TESTSUITE/test-mail/junk
+rda_interpret (string): 'TESTSUITE/test-mail/junk'
+expanded: 'TESTSUITE/test-mail/junk'
 file is not a filter file
 parse_forward_list: TESTSUITE/test-mail/junk
 extract item: TESTSUITE/test-mail/junk
@@ -356,6 +358,7 @@ userz in "userz"? yes (matched "userz")
  ╭considering: /non-exist/$domain
  ├──expanding: /non-exist/$domain
  ╰─────result: /non-exist/test.ex
+            ╰──(tainted)
 calling r3 router
 r3 router called for userz@???
   domain = test.ex
@@ -381,6 +384,7 @@ usery in "usery"? yes (matched "usery")
  ╭considering: /non-exist/$domain
  ├──expanding: /non-exist/$domain
  ╰─────result: /non-exist/test.ex
+            ╰──(tainted)
 calling r2 router
 r2 router called for usery@???
   domain = test.ex
@@ -401,6 +405,7 @@ CALLER in "CALLER"? yes (matched "CALLER")
  ╭considering: /non-exist/$local_part
  ├──expanding: /non-exist/$local_part
  ╰─────result: /non-exist/CALLER
+            ╰──(tainted)
 calling r1 router
 r1 router called for CALLER@???
   domain = test.ex
@@ -449,6 +454,7 @@ no retry data available
  ╭considering: /non-exist/$local_part
  ├──expanding: /non-exist/$local_part
  ╰─────result: /non-exist/usery
+            ╰──(tainted)
 search_tidyup called
 changed uid/gid: local delivery to TESTSUITE/test-mail/junk <TESTSUITE/test-mail/junk> transport=ft1
   uid=CALLER_UID gid=CALLER_GID pid=pppp
@@ -477,6 +483,7 @@ writing to file TESTSUITE/test-mail/junk


   ├──expanding: $return_path
   ╰─────result: CALLER@???
+             ╰──(tainted)
   ╭───scanning: MAILER-DAEMON}} ${tod_bsdinbox}


├──expanding: MAILER-DAEMON
@@ -486,6 +493,7 @@ writing to file TESTSUITE/test-mail/junk

╰─────result: From CALLER@??? Tue Mar 02 09:44:33 1999

+            ╰──(tainted)
 writing data block fd=dddd size=sss timeout=0
 cannot use sendfile for body: spoolfile not wireformat
 writing data block fd=dddd size=sss timeout=0
@@ -534,6 +542,7 @@ writing to file TESTSUITE/test-mail/junk


   ├──expanding: $return_path
   ╰─────result: CALLER@???
+             ╰──(tainted)
   ╭───scanning: MAILER-DAEMON}} ${tod_bsdinbox}


├──expanding: MAILER-DAEMON
@@ -543,6 +552,7 @@ writing to file TESTSUITE/test-mail/junk

╰─────result: From CALLER@??? Tue Mar 02 09:44:33 1999

+            ╰──(tainted)
 writing data block fd=dddd size=sss timeout=0
 cannot use sendfile for body: spoolfile not wireformat
 writing data block fd=dddd size=sss timeout=0
@@ -592,6 +602,7 @@ no retry data available
  ╭considering: /non-exist/$local_part
  ├──expanding: /non-exist/$local_part
  ╰─────result: /non-exist/usery
+            ╰──(tainted)
 search_tidyup called
 changed uid/gid: local delivery to usery <usery@???> transport=t1
   uid=CALLER_UID gid=CALLER_GID pid=pppp
@@ -621,6 +632,7 @@ no retry data available
  ╭considering: /$local_part
  ├──expanding: /$local_part
  ╰─────result: /userz
+            ╰──(tainted)
 search_tidyup called
 changed uid/gid: local delivery to userz <userz@???> transport=t2
   uid=CALLER_UID gid=CALLER_GID pid=pppp
diff --git a/test/stderr/0403 b/test/stderr/0403
index e0fc74a..29a5558 100644
--- a/test/stderr/0403
+++ b/test/stderr/0403
@@ -120,8 +120,8 @@ userx in "lsearch;TESTSUITE/aux-fixed/0403.data"? yes (matched "lsearch;TESTSUIT
 +++home=/usr
 processing address_data
 calling r1 router
-rda_interpret (string): TESTSUITE/test-mail/junk
-expanded: TESTSUITE/test-mail/junk
+rda_interpret (string): 'TESTSUITE/test-mail/junk'
+expanded: 'TESTSUITE/test-mail/junk'
 file is not a filter file
 parse_forward_list: TESTSUITE/test-mail/junk
 extract item: TESTSUITE/test-mail/junk
diff --git a/test/stderr/0404 b/test/stderr/0404
index 652e653..6ec697b 100644
--- a/test/stderr/0404
+++ b/test/stderr/0404
@@ -193,10 +193,10 @@ r1 router skipped: local_parts mismatch
 --------> r2 router <--------
 local_part=userx domain=test.ex
 calling r2 router
-rda_interpret (string): #Exim filter\nmail text rhubarb\nseen finish
-expanded: #Exim filter
+rda_interpret (string): '#Exim filter\nmail text rhubarb\nseen finish'
+expanded: '#Exim filter
 mail text rhubarb
-seen finish
+seen finish'
 search_tidyup called
 changed uid/gid: r2 router (recipient is userx@???)
   uid=CALLER_UID gid=CALLER_GID pid=pppp
diff --git a/test/stderr/0426 b/test/stderr/0426
index 9918563..dda5191 100644
--- a/test/stderr/0426
+++ b/test/stderr/0426
@@ -73,8 +73,8 @@ routing CALLER@???
 local_part=CALLER domain=test.ex
 checking senders
 calling r0 router
-rda_interpret (string): :blackhole:
-expanded: :blackhole:
+rda_interpret (string): ':blackhole:'
+expanded: ':blackhole:'
 file is not a filter file
 parse_forward_list: :blackhole:
 extract item: :blackhole:
diff --git a/test/stderr/0464 b/test/stderr/0464
index 729f872..512ed1b 100644
--- a/test/stderr/0464
+++ b/test/stderr/0464
@@ -49,8 +49,8 @@ cached yes match for +special_domains
 cached lookup data = data for domain1
 domain1 in "+special_domains"? yes (matched "+special_domains" - cached)
 calling r1 router
-rda_interpret (string): $local_part@xxx.$domain
-expanded: abc@???
+rda_interpret (string): '$local_part@xxx.$domain'
+expanded: 'abc@???'
 file is not a filter file
 parse_forward_list: abc@???
 extract item: abc@???
@@ -116,8 +116,8 @@ cached yes match for +special_domains
 cached lookup data = data for domain1
 domain1 in "+special_domains"? yes (matched "+special_domains" - cached)
 calling r1 router
-rda_interpret (string): $local_part@xxx.$domain
-expanded: abc@???
+rda_interpret (string): '$local_part@xxx.$domain'
+expanded: 'abc@???'
 file is not a filter file
 parse_forward_list: abc@???
 extract item: abc@???
diff --git a/test/stderr/0544 b/test/stderr/0544
index c90d9b1..cce0311 100644
--- a/test/stderr/0544
+++ b/test/stderr/0544
@@ -128,6 +128,7 @@ dropping to exim gid; retaining priv uid
  ╭considering: $domain
  ├──expanding: $domain
  ╰─────result: domain1.ex
+            ╰──(tainted)
 LOG: MAIN
   == userx@??? R=smarthost T=smtp defer (-1): domain matches queue_smtp_domains, or -odqs set
 LOG: MAIN
@@ -176,12 +177,14 @@ LOG: smtp_connection MAIN
   ╭considering: $sender_helo_name}{+dlist}}
   ├──expanding: $sender_helo_name
   ╰─────result: ehlo.domain
+             ╰──(tainted)
   ╭considering: +dlist}}
   ├──expanding: +dlist
   ╰─────result: +dlist
   ╭considering: $domain
   ├──expanding: $domain
   ╰─────result: ehlo.domain
+             ╰──(tainted)
  ├──condition: match_domain {$sender_helo_name}{+dlist}
  ├─────result: true
  ├──expanding: ${if match_domain {$sender_helo_name}{+dlist}}
@@ -189,12 +192,15 @@ LOG: smtp_connection MAIN
  ╭considering: domain=$domain/sender_domain=$sender_address_domain
  ├──expanding: domain=$domain/sender_domain=$sender_address_domain
  ╰─────result: domain=/sender_domain=sender.domain
+            ╰──(tainted)
  ╭considering: domain=$domain/sender_domain=$sender_address_domain
  ├──expanding: domain=$domain/sender_domain=$sender_address_domain
  ╰─────result: domain=recipient.domain/sender_domain=sender.domain
+            ╰──(tainted)
  ╭considering: domain=$domain/sender_domain=$sender_address_domain
  ├──expanding: domain=$domain/sender_domain=$sender_address_domain
  ╰─────result: domain=recipient.domain/sender_domain=sender.domain
+            ╰──(tainted)
 LOG: smtp_connection MAIN
   SMTP connection from CALLER closed by QUIT

>>>>>>>>>>>>>>>> Exim pid=pppp (msg setup toplevel) terminating with rc=0 >>>>>>>>>>>>>>>>

diff --git a/test/stderr/1003 b/test/stderr/1003
index c83c508..8059919 100644
--- a/test/stderr/1003
+++ b/test/stderr/1003
@@ -1,3 +1,3 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=client T=send_to_server defer (-1): failed to expand "interface" option for send_to_server transport: internal expansion of "<; ${if" failed: condition name expected, but found ""
+1999-03-02 09:44:33 10HmaX-0005vi-00 == user6@??? R=client T=send_to_server5 defer (-1): failed to expand "interface" option for send_to_server5 transport: condition name expected, but found ""

 ******** SERVER ********
diff --git a/test/stderr/5000 b/test/stderr/5000
index 2bca5ea..2b96d89 100644
--- a/test/stderr/5000
+++ b/test/stderr/5000
@@ -1,4 +1,4 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${expand:$h_tag:}" (maildir_tag for maildir_tagged_appendfile transport) failed: internal expansion of "${if eq{0}{1}{rhubarb}" failed: syntax error in "if" item - "fail" expected
+1999-03-02 09:44:33 10HmaX-0005vi-00 == userx@??? R=localuser T=maildir_tagged_appendfile defer (-1): Expansion of "${if eq{0}{1}{rhubarb}" (maildir_tag for maildir_tagged_appendfile transport) failed: syntax error in "if" item - "fail" expected
 Exim version x.yz ....
 configuration file is TESTSUITE/test-config
 admin user
diff --git a/test/stderr/5004 b/test/stderr/5004
index 465f98a..c3e489a 100644
--- a/test/stderr/5004
+++ b/test/stderr/5004
@@ -91,10 +91,9 @@ routing userx@???
 --------> r1 router <--------
 local_part=userx domain=test.ex
 calling r1 router
-rda_interpret (string): # Exim filter
-save TESTSUITE/test-mail
-expanded: # Exim filter
-save TESTSUITE/test-mail
+rda_interpret (string): '# Exim filter\nsave TESTSUITE/test-mail'
+expanded: '# Exim filter
+save TESTSUITE/test-mail'
 search_tidyup called
 changed uid/gid: r1 router (recipient is userx@???)
   uid=CALLER_UID gid=CALLER_GID pid=pppp
diff --git a/test/stderr/5204 b/test/stderr/5204
index 7c2109d..1f927a4 100644
--- a/test/stderr/5204
+++ b/test/stderr/5204
@@ -187,8 +187,8 @@ calling q router
 q router called for "REDIRECT postmaster@???: domain = some.host
 requires uid=EXIM_UID gid=EXIM_GID current_directory=/
 command wrote: REDIRECT postmaster@???
-rda_interpret (string): postmaster@???
-expanded: postmaster@???
+rda_interpret (string): 'postmaster@???'
+expanded: 'postmaster@???'
 file is not a filter file
 parse_forward_list: postmaster@???
 extract item: postmaster@???
diff --git a/test/stderr/5410 b/test/stderr/5410
index 7978a02..a554fd9 100644
--- a/test/stderr/5410
+++ b/test/stderr/5410
@@ -38,6 +38,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: userx
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -48,6 +49,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: userx
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -67,6 +69,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: userx
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -86,6 +89,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{userz}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: userx
+             ╰──(tainted)
   ╭considering: userz}{*}{:}}
   ├──expanding: userz
   ╰─────result: userz
@@ -191,10 +195,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -233,6 +239,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for userx@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -245,6 +252,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmaX-0005vi-00
      for userx@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
@@ -305,6 +313,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -315,6 +324,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -334,6 +344,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: usery
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -427,10 +438,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -469,6 +482,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for usery@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -481,6 +495,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmaZ-0005vi-00
      for usery@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
@@ -541,6 +556,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -551,6 +567,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -570,6 +587,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: usery
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -663,10 +681,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -705,6 +725,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for usery@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -717,6 +738,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmbB-0005vi-00
      for usery@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
diff --git a/test/stderr/5420 b/test/stderr/5420
index 9b51bff..9aefc24 100644
--- a/test/stderr/5420
+++ b/test/stderr/5420
@@ -38,6 +38,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: userx
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -48,6 +49,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: userx
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -67,6 +69,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: userx
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -86,6 +89,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{userz}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: userx
+             ╰──(tainted)
   ╭considering: userz}{*}{:}}
   ├──expanding: userz
   ╰─────result: userz
@@ -192,10 +196,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -234,6 +240,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for userx@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -246,6 +253,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmaX-0005vi-00
      for userx@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
@@ -306,6 +314,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -316,6 +325,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -335,6 +345,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: usery
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -428,10 +439,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -470,6 +483,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for usery@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -482,6 +496,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmaZ-0005vi-00
      for usery@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
@@ -542,6 +557,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 ----------- end verify ------------
 accept: condition test succeeded in ACL "cutthrough"
@@ -552,6 +568,7 @@ domain.com in "! +local_domains"? yes (end of list)
  ╭considering: $local_part
  ├──expanding: $local_part
  ╰─────result: usery
+            ╰──(tainted)
 domain.com in "*"? yes (matched "*")
 Connecting to 127.0.0.1 [127.0.0.1]:1225 from ip4.ip4.ip4.ip4 ...  connected
  ╭considering: $primary_hostname
@@ -571,6 +588,7 @@ cmd buf flush ddd bytes
   ╭considering: $address_data}{usery}{*}{:}}
   ├──expanding: $address_data
   ╰─────result: usery
+             ╰──(tainted)
   ╭considering: usery}{*}{:}}
   ├──expanding: usery
   ╰─────result: usery
@@ -664,10 +682,12 @@ end of inline ACL: ACCEPT

    
    ╰─────result: (helo=myhost.test.ex)

    
+              ╰──(tainted)
   ├──expanding: ${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
       }}
   ╰─────result: from CALLER (helo=myhost.test.ex)

    
+             ╰──(tainted)
  ├──condition: def:received_protocol
  ├─────result: true
   ╭considering: with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -706,6 +726,7 @@ end of inline ACL: ACCEPT
       for $received_for
   ╰─────result: 
       for usery@???
+             ╰──(tainted)
  ├──expanding: Received: ${if def:sender_rcvhost {from $sender_rcvhost
      }{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)
      }}}}by $primary_hostname ${if def:received_protocol {with $received_protocol }}${if def:tls_in_cipher_std { tls $tls_in_cipher_std
@@ -718,6 +739,7 @@ end of inline ACL: ACCEPT
      (envelope-from <CALLER@???>)
      id 10HmbB-0005vi-00
      for usery@???
+            ╰──(tainted)
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
  ╭considering: ${tod_full}
diff --git a/test/stdout/0002 b/test/stdout/0002
index c1f9d37..6319fdc 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -873,6 +873,7 @@ xyz

> escape: B7?F2?
>
> primary_hostname: myhost.test.ex

+> sender_address: sndr@dom
> match: cdab
> Failed: "if" failed and "fail" requested
> yes

diff --git a/test/stdout/0035 b/test/stdout/0035
index 60e2c62..09b5df4 100644
--- a/test/stdout/0035
+++ b/test/stdout/0035
@@ -145,7 +145,7 @@ EXIMUSER EXIM_UID EXIM_GID
<notsubmit@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -166,7 +166,7 @@ EXIMUSER EXIM_UID EXIM_GID
<a@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -190,7 +190,7 @@ EXIMUSER EXIM_UID EXIM_GID
<>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -212,7 +212,7 @@ EXIMUSER EXIM_UID EXIM_GID
<notsubmit@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -234,7 +234,7 @@ EXIMUSER EXIM_UID EXIM_GID
<a@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -259,7 +259,7 @@ EXIMUSER EXIM_UID EXIM_GID
<a@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
@@ -284,7 +284,7 @@ EXIMUSER EXIM_UID EXIM_GID
<a@y>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-interface_address 127.0.0.1.1225
-received_protocol esmtp
diff --git a/test/stdout/3415 b/test/stdout/3415
index 8e20252..0889dd7 100644
--- a/test/stdout/3415
+++ b/test/stdout/3415
@@ -156,14 +156,14 @@ EXIMUSER EXIM_UID EXIM_GID
<username@???>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-host_auth au1
-interface_address 127.0.0.1.1225
-received_protocol esmtpa
-body_linecount 0
-max_received_linelength 0
--auth_id username
+--auth_id username
-deliver_firsttime
XX
1
@@ -183,14 +183,14 @@ EXIMUSER EXIM_UID EXIM_GID
<>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-host_auth au1
-interface_address 127.0.0.1.1225
-received_protocol esmtpa
-body_linecount 0
-max_received_linelength 0
--auth_id username
+--auth_id username
-deliver_firsttime
XX
1
@@ -208,14 +208,14 @@ EXIMUSER EXIM_UID EXIM_GID
<>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-host_auth au1
-interface_address 127.0.0.1.1225
-received_protocol esmtpa
-body_linecount 0
-max_received_linelength 0
--auth_id username
+--auth_id username
-deliver_firsttime
XX
1
@@ -233,14 +233,14 @@ EXIMUSER EXIM_UID EXIM_GID
<>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-host_auth au1
-interface_address 127.0.0.1.1225
-received_protocol esmtpa
-body_linecount 0
-max_received_linelength 0
--auth_id username@???
+--auth_id username@???
-deliver_firsttime
XX
1
@@ -258,14 +258,14 @@ EXIMUSER EXIM_UID EXIM_GID
<>
ddddddddd 0
-received_time_usec .uuuuuu
--helo_name rhu.barb
+--helo_name rhu.barb
-host_address 127.0.0.1.9999
-host_auth au1
-interface_address 127.0.0.1.1225
-received_protocol esmtpa
-body_linecount 0
-max_received_linelength 15
--auth_id username@???
+--auth_id username@???
-deliver_firsttime
XX
1