Track tainted data and refuse to expand it
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 25 Jul 2019 11:06:07 +0000 (12:06 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 25 Jul 2019 11:06:07 +0000 (12:06 +0100)
205 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/OS/unsupported/os.c-IRIX
src/OS/unsupported/os.c-IRIX6
src/OS/unsupported/os.c-IRIX632
src/OS/unsupported/os.c-IRIX65
src/OS/unsupported/os.c-cygwin
src/exim_monitor/em_log.c
src/exim_monitor/em_main.c
src/exim_monitor/em_menu.c
src/exim_monitor/em_queue.c
src/exim_monitor/em_strip.c
src/exim_monitor/em_version.c
src/exim_monitor/em_xs.c
src/src/acl.c
src/src/arc.c
src/src/auths/call_pam.c
src/src/auths/cram_md5.c
src/src/auths/cyrus_sasl.c
src/src/auths/heimdal_gssapi.c
src/src/auths/pwcheck.c
src/src/auths/xtextdecode.c
src/src/auths/xtextencode.c
src/src/base64.c
src/src/bmi_spam.c
src/src/child.c
src/src/daemon.c
src/src/dane-openssl.c
src/src/dbfn.c
src/src/dcc.c
src/src/debug.c
src/src/deliver.c
src/src/dkim.c
src/src/dmarc.c
src/src/dns.c
src/src/drtables.c
src/src/dummies.c
src/src/environment.c
src/src/exim.c
src/src/exim_dbmbuild.c
src/src/exim_dbutil.c
src/src/expand.c
src/src/filter.c
src/src/filtertest.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/hash.c
src/src/header.c
src/src/host.c
src/src/ip.c
src/src/local_scan.h
src/src/log.c
src/src/lookups/cdb.c
src/src/lookups/dbmdb.c
src/src/lookups/dnsdb.c
src/src/lookups/ibase.c
src/src/lookups/json.c
src/src/lookups/ldap.c
src/src/lookups/lmdb.c
src/src/lookups/lsearch.c
src/src/lookups/mysql.c
src/src/lookups/nis.c
src/src/lookups/nisplus.c
src/src/lookups/oracle.c
src/src/lookups/pgsql.c
src/src/lookups/redis.c
src/src/lookups/sqlite.c
src/src/macros.h
src/src/malware.c
src/src/match.c
src/src/mime.c
src/src/moan.c
src/src/mytypes.h
src/src/os.c
src/src/parse.c
src/src/pdkim/pdkim.c
src/src/pdkim/signing.c
src/src/queue.c
src/src/rda.c
src/src/readconf.c
src/src/receive.c
src/src/regex.c
src/src/retry.c
src/src/rewrite.c
src/src/rfc2047.c
src/src/route.c
src/src/routers/dnslookup.c
src/src/routers/ipliteral.c
src/src/routers/iplookup.c
src/src/routers/manualroute.c
src/src/routers/redirect.c
src/src/routers/rf_change_domain.c
src/src/routers/rf_get_munge_headers.c
src/src/routers/rf_get_transport.c
src/src/routers/rf_queue_add.c
src/src/search.c
src/src/setenv.c
src/src/sieve.c
src/src/smtp_in.c
src/src/smtp_out.c
src/src/spam.c
src/src/spool_in.c
src/src/spool_mbox.c
src/src/spool_out.c
src/src/store.c
src/src/store.h
src/src/string.c
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/tlscert-gnu.c
src/src/tlscert-openssl.c
src/src/transport.c
src/src/transports/appendfile.c
src/src/transports/appendfile.h
src/src/transports/autoreply.c
src/src/transports/lmtp.c
src/src/transports/pipe.c
src/src/transports/smtp.c
src/src/transports/smtp.h
src/src/transports/tf_maildir.c
src/src/tree.c
src/src/utf8.c
src/src/verify.c
src/src/version.c
test/confs/0102
test/confs/0137
test/confs/0140
test/confs/0284
test/confs/0386
test/confs/0428
test/confs/0478
test/confs/0504
test/confs/0610
test/confs/1003
test/confs/5000
test/confs/5050
test/confs/5103
test/log/0137
test/log/0428
test/log/0610
test/log/1003
test/log/5000
test/mail/0137.userx
test/mail/0428.inbox.JUNK
test/mail/0428.someone
test/mail/0428.userx
test/mail/0428.userx-sawsuffix
test/mail/0428.userx13 [new file with mode: 0644]
test/mail/0428.userx14 [new file with mode: 0644]
test/mail/0428.userx9 [new file with mode: 0644]
test/mail/5000.new/1.myhost.test.ex
test/mail/5000.new/2.myhost.test.ex
test/mail/5000.new/3.myhost.test.ex:S370
test/mail/5000.new/4.myhost.test.ex,S=370
test/mail/5000.new/5.myhost.test.ex
test/mail/5000.new/6.myhost.test.ex
test/mail/5000.new/7.myhost.test.ex,S=10694953:2,S
test/msglog/5000.10HmaX-0005vi-00
test/paniclog/1003
test/paniclog/5000
test/runtest
test/scripts/0000-Basic/0002
test/scripts/0000-Basic/0137
test/scripts/0000-Basic/0428
test/scripts/0000-Basic/0504
test/scripts/1000-Basic-ipv6/1003
test/scripts/5000-maildir/5000
test/scripts/5100-lmtp-transport/5103
test/stderr/0002
test/stderr/0023
test/stderr/0037
test/stderr/0084
test/stderr/0085
test/stderr/0123
test/stderr/0279
test/stderr/0297
test/stderr/0360
test/stderr/0361
test/stderr/0364
test/stderr/0370
test/stderr/0377
test/stderr/0378
test/stderr/0379
test/stderr/0380
test/stderr/0382
test/stderr/0386
test/stderr/0388
test/stderr/0393
test/stderr/0399
test/stderr/0402
test/stderr/0403
test/stderr/0404
test/stderr/0426
test/stderr/0464
test/stderr/0544
test/stderr/1003
test/stderr/5000
test/stderr/5004
test/stderr/5204
test/stderr/5410
test/stderr/5420
test/stdout/0002
test/stdout/0035
test/stdout/3415

index c4d6112..32d57d0 100644 (file)
@@ -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%&
index 2e83903..78cb127 100644 (file)
@@ -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
 -----------------
index 1f6b0e1..c1539cb 100644 (file)
@@ -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",
index 1f6b0e1..c1539cb 100644 (file)
@@ -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",
index 1f6b0e1..c1539cb 100644 (file)
@@ -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",
index 1f6b0e1..c1539cb 100644 (file)
@@ -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",
index c9464aa..5ca05a8 100644 (file)
@@ -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') {
index 52eef6b..1e1dc7c 100644 (file)
@@ -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)
index 7aa760e..9c7f442 100644 (file)
@@ -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");
index 31ce1a3..92e0b35 100644 (file)
@@ -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)
index c8d9a40..f121527 100644 (file)
@@ -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)
   {
index 2a5f0b8..03864d2 100644 (file)
@@ -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;
index e5a4ebb..52c55a4 100644 (file)
@@ -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
 }
 
index b145fb9..ee91f7c 100644 (file)
@@ -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 */
index 3788612..dac2ba5 100644 (file)
@@ -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);
 }
 
index 7bdf4bf..c266849 100644 (file)
@@ -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;
index 204f449..9a46b6b 100644 (file)
@@ -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;
     }
index b1c5610..a60db56 100644 (file)
@@ -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);
   }
index bce4202..480010b 100644 (file)
@@ -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
index a70bc8a..273d4f4 100644 (file)
@@ -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);
   }
 
index 54ba80f..f733b98 100644 (file)
@@ -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;
index 5312acf..95cf5da 100644 (file)
@@ -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)
index 2c00c4a..30ff8f1 100644 (file)
@@ -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;
index 289383b..6c81914 100644 (file)
@@ -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)
index 546ac1e..6651de5 100644 (file)
@@ -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;
index e53e448..d3cd882 100644 (file)
@@ -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. */
index 0b4d347..21ce2f0 100644 (file)
@@ -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)
index c0bbf3d..9b86a48 100644 (file)
@@ -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;
index a607756..63a1aef 100644 (file)
@@ -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);
 
index 5aa2b17..4c9c096 100644 (file)
@@ -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);
index eb62157..de97962 100644 (file)
@@ -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;
index a597c9a..ba9572e 100644 (file)
@@ -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;
     }
index 715774c..a410ed5 100644 (file)
@@ -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? */
 }
 
index 5bf3303..be9c4b3 100644 (file)
@@ -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,
index 6ef6b77..e384597 100644 (file)
@@ -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;
     }
index b702429..0597562 100644 (file)
@@ -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;
 }
index 2e1ad11..197415f 100644 (file)
@@ -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;
index 9d3d126..c29cc6c 100644 (file)
@@ -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;
 }
index 7571705..f163b12 100644 (file)
@@ -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)
index 39207be..311d961 100644 (file)
@@ -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);
index 2c7aad6..8b71a41 100644 (file)
@@ -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);
index 65c585d..1bfc75d 100644 (file)
@@ -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();
index a16416c..3da6167 100644 (file)
@@ -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;
 }
index f3d3acc..f54cbef 100644 (file)
@@ -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);
 
index 806ba75..bcf04c2 100644 (file)
@@ -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;
 }
 
 /******************************************************************************/
index 742584e..15fb089 100644 (file)
@@ -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;
 
index 80e1764..c226004 100644 (file)
@@ -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 */
index 1bdeaef..f1a6c40 100644 (file)
@@ -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);
 }
index 76ea10f..a6c44fa 100644 (file)
@@ -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;
index 9d94a2f..a00d048 100644 (file)
@@ -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;
index 0f25df1..19be51a 100644 (file)
@@ -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)
   {
index dced8bd..b250a16 100644 (file)
@@ -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
index 0aaf94a..e2543a7 100644 (file)
@@ -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--;
index 13fe8b7..5cae153 100644 (file)
@@ -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;
index bdc002d..5e97009 100644 (file)
@@ -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';
 
index 70203fa..5654b56 100644 (file)
@@ -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. */
index eab3c6a..f08f503 100644 (file)
@@ -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) {
index a02b7b9..15b8617 100644 (file)
@@ -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;
 }
index 97ee188..5b0cffa 100644 (file)
@@ -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;
     }
   }
index 931ea5b..2976cfa 100644 (file)
@@ -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)))
index 8b4459a..76b76b8 100644 (file)
@@ -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;
   }
index 1984e30..460ee29 100644 (file)
@@ -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)
   {
index d3f0480..6f5307f 100644 (file)
@@ -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;
index 6a3351e..98f3df3 100644 (file)
@@ -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)
   {
index d106c51..4e8cba5 100644 (file)
@@ -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)
   {
index cf1e174..b5f6093 100644 (file)
@@ -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)
   {
index 6de6757..1b53eed 100644 (file)
@@ -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)
   {
index 5da2de8..6200d6c 100644 (file)
@@ -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)
   {
index 2c7900b..a94a71f 100644 (file)
@@ -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 */
index 91649cf..481b46a 100644 (file)
@@ -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 */
index 43f5912..4cd9259 100644 (file)
@@ -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;
 
index cf537d7..d47b569 100644 (file)
@@ -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... */
index cdec745..fea3683 100644 (file)
@@ -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);
 
index be11240..84baa9e 100644 (file)
@@ -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 */
index 9c1281a..c44bd3e 100644 (file)
@@ -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;
index 4b0efa0..e64cb94 100644 (file)
@@ -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;
index 9ebcfc1..239532b 100644 (file)
@@ -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;
 }
 
index 53a8a7b..de64ee4 100644 (file)
@@ -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
index f54124c..a124782 100644 (file)
@@ -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
     )
   {
index 228f574..42b7b14 100644 (file)
@@ -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;
index a7cf03d..52c6483 100644 (file)
@@ -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;
 
index ed2afb3..c7553f8 100644 (file)
@@ -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;