]> git.netwichtig.de Git - user/henk/code/exim.git/commitdiff
Merge branch 'hs/taintwarn'
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Fri, 25 Jun 2021 08:02:47 +0000 (10:02 +0200)
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Fri, 25 Jun 2021 08:02:47 +0000 (10:02 +0200)
This is a "forward" port of the taintwarn patches that are applied to
4.94.2+fixes.

34 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/EDITME
src/src/acl.c
src/src/config.h.defaults
src/src/dbstuff.h
src/src/deliver.c
src/src/directory.c
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/log.c
src/src/lookups/lf_sqlperform.c
src/src/macros.h
src/src/parse.c
src/src/rda.c
src/src/readconf.c
src/src/routers/rf_get_transport.c
src/src/search.c
src/src/smtp_out.c
src/src/transports/appendfile.c
src/src/transports/autoreply.c
src/src/transports/pipe.c
src/src/transports/smtp.c
test/aux-fixed/0990/example.com [new file with mode: 0644]
test/confs/0990 [new file with mode: 0644]
test/log/0608
test/paniclog/0608
test/scripts/0990-Allow-Tainted-Data/0990 [new file with mode: 0644]
test/scripts/0990-Allow-Tainted-Data/REQUIRES [new file with mode: 0644]
test/stderr/0608
test/stderr/0990 [new file with mode: 0644]
test/stdout/0990 [new file with mode: 0644]

index c594687162dd9179ec7e0cc2133451a833acb34f..05d8e6ed17511ac2959b1fca616c2923561a2e53 100644 (file)
   <secondary>failure report</secondary>
   <see><emphasis>bounce message</emphasis></see>
 </indexterm>
+<indexterm role="concept">
+  <primary>de-tainting</primary>
+  <see><emphasis>tainting, de-tainting</emphasis></see>
+</indexterm>
+<indexterm role="concept">
+  <primary>detainting</primary>
+  <see><emphasis>tainting, de-tainting</emphasis></see>
+</indexterm>
 <indexterm role="concept">
   <primary>dialup</primary>
   <see><emphasis>intermittently connected hosts</emphasis></see>
@@ -9606,6 +9614,8 @@ reasons,
 and expansion of data deriving from the sender (&"tainted data"&)
 .new
 is not permitted (including acessing a file using a tainted name).
+The main config option &%allow_insecure_tainted_data%& can be used as
+mitigation during uprades to more secure configurations.
 .wen
 
 .new
@@ -14590,6 +14600,7 @@ listed in more than one group.
 .section "Miscellaneous" "SECID96"
 .table2
 .row &%add_environment%&             "environment variables"
+.row &%allow_insecure_tainted_data%& "turn taint errors into warnings"
 .row &%bi_command%&                  "to run for &%-bi%& command line option"
 .row &%debug_store%&                 "do extra internal checks"
 .row &%disable_ipv6%&                "do no IPv6 processing"
@@ -15201,6 +15212,18 @@ domains (defined in the named domain list &%local_domains%& in the default
 configuration). This &"magic string"& matches the domain literal form of all
 the local host's IP addresses.
 
+.new
+.option allow_insecure_tainted_data main boolean false
+.cindex "de-tainting"
+.oindex "allow_insecure_tainted_data"
+The handling of tainted data may break older (pre 4.94) configurations.
+Setting this option to "true" turns taint errors (which result in a temporary
+message rejection) into warnings. This option is meant as mitigation only
+and deprecated already today. Future releases of Exim may ignore it.
+The &%taint%& log selector can be used to suppress even the warnings.
+.wen
+
+
 
 .option allow_mx_to_ip main boolean false
 .cindex "MX record" "pointing to IP address"
@@ -38750,6 +38773,7 @@ selection marked by asterisks:
 &` smtp_protocol_error        `&  SMTP protocol errors
 &` smtp_syntax_error          `&  SMTP syntax errors
 &` subject                    `&  contents of &'Subject:'& on <= lines
+&`*taint                      `&  taint errors or warnings
 &`*tls_certificate_verified   `&  certificate verification status
 &`*tls_cipher                 `&  TLS cipher suite on <= and => lines
 &` tls_peerdn                 `&  TLS peer DN on <= and => lines
@@ -39145,6 +39169,11 @@ using a CA trust anchor,
 &`CV=dane`& if using a DNS trust anchor,
 and &`CV=no`& if not.
 .next
+.cindex "log" "Taint warnings"
+&%taint%&: Log warnings about tainted data. This selector can't be
+turned of if &%allow_insecure_tainted_data%& is false (which is the
+default).
+.next
 .cindex "log" "TLS cipher"
 .cindex "TLS" "logging cipher"
 &%tls_cipher%&: When a message is sent or received over an encrypted
index 46a69c1d7e2cc7e6603e89e4d372e0cfb2c8b5a0..0ef9ab25e221644ee2e698593100dbec3a9bd834 100644 (file)
@@ -56,6 +56,51 @@ Version 4.95
 
 16. Main option "hosts_require_helo", requiring HELO or EHLO before MAIL.
 
+Version 4.95
+------------
+
+ 1. The fast-ramp two phase queue run support, previously experimental, is
+    now supported by default.
+
+ 2. The native SRS support, previously experimental, is now supported. It is
+    not built unless specified in the Local/Makefile.
+
+ 3. TLS resumption support, previously experimental, is now supported and
+    included in default builds.
+
+ 4. Single-key LMDB lookups, previously experimental, are now supported.
+    The support is not built unless specified in the Local/Makefile.
+
+ 5. Option "message_linelength_limit" on the smtp transport to enforce (by
+    default) the RFC 998 character limit.
+
+ 6. An option to ignore the cache on a lookup.
+
+ 7. Quota checking during reception (i.e. at SMTP time) for appendfile-
+    transport-managed quotas.
+
+ 8. Sqlite lookups accept a "file=<path>" option to specify a per-operation
+    db file, replacing the previous prefix to the SQL string (which had
+    issues when the SQL used tainted values).
+
+ 9. Lsearch lookups accept a "ret=full" option, to return both the portion
+    of the line matching the key, and the remainder.
+
+10. A command-line option to have a daemon not create a notifier socket.
+
+11. Faster TLS startup.  When various configuration options contain no
+    expandable elements, the information can be preloaded and cached rather
+    than the provious behaviour of always loading at startup time for every
+    connection.  This helps particularly for the CA bundle.
+
+12. Proxy Protocol Timeout is configurable via "proxy_protocol_timeout"
+    main config option.
+
+13. Option "smtp_accept_msx_per_connection" is now expanded.
+
+13. A main config option "allow_insecure_tainted_data" allows to turn
+    taint errors into warnings.
+
 Version 4.94
 ------------
 
index 8cd34e8be9f49899dda416342a78b42932645796..f4329fabf53beff6e4851144482281ef16c7ebe2 100644 (file)
@@ -748,6 +748,13 @@ FIXED_NEVER_USERS=root
 
 # WHITELIST_D_MACROS=TLS:SPOOL
 
+# The next setting enables a main config option
+# "allow_insecure_tainted_data" to turn taint failures into warnings.
+# Though this option is new, it is deprecated already now, and will be
+# ignored in future releases of Exim. It is meant as mitigation for
+# upgrading old (possibly insecure) configurations to more secure ones.
+ALLOW_INSECURE_TAINTED_DATA=yes
+
 #------------------------------------------------------------------------------
 # Exim has support for the AUTH (authentication) extension of the SMTP
 # protocol, as defined by RFC 2554. If you don't know what SMTP authentication
index f358516a1983afda619ef316850a08050db4b948..1bf118764baa1b1e67b8f38fa8dd4df56f9d9695 100644 (file)
@@ -3702,20 +3702,22 @@ for (; cb; cb = cb->next)
     #endif
 
     case ACLC_QUEUE:
-    if (is_tainted(arg))
       {
-      *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
-                                   arg);
-      return ERROR;
-      }
-    if (Ustrchr(arg, '/'))
-      {
-      *log_msgptr = string_sprintf(
-             "Directory separator not permitted in queue name: '%s'", arg);
-      return ERROR;
+      uschar *m;
+      if ((m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg)))
+        {
+        *log_msgptr = m;
+        return ERROR;
+        }
+      if (Ustrchr(arg, '/'))
+        {
+        *log_msgptr = string_sprintf(
+                "Directory separator not permitted in queue name: '%s'", arg);
+        return ERROR;
+        }
+      queue_name = string_copy_perm(arg, FALSE);
+      break;
       }
-    queue_name = string_copy_perm(arg, FALSE);
-    break;
 
     case ACLC_RATELIMIT:
     rc = acl_ratelimit(arg, where, log_msgptr);
@@ -4088,25 +4090,14 @@ while (isspace(*ss)) ss++;
 
 acl_text = ss;
 
-#ifdef notyet_taintwarn
 if (  !f.running_in_test_harness
    &&  is_tainted2(acl_text, LOG_MAIN|LOG_PANIC,
-                         "attempt to use tainted ACL text \"%s\"", acl_text))
+                         "Tainted ACL text \"%s\"", acl_text))
   {
   /* Avoid leaking info to an attacker */
   *log_msgptr = US"internal configuration error";
   return ERROR;
   }
-#else
-if (is_tainted(acl_text) && !f.running_in_test_harness)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "attempt to use tainted ACL text \"%s\"", acl_text);
-  /* Avoid leaking info to an attacker */
-  *log_msgptr = US"internal configuration error";
-  return ERROR;
-  }
-#endif
 
 /* Handle the case of a string that does not contain any spaces. Look for a
 named ACL among those read from the configuration, or a previously read file.
@@ -4131,6 +4122,12 @@ if (Ustrchr(ss, ' ') == NULL)
   else if (*ss == '/')
     {
     struct stat statbuf;
+    if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "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,
index e233fb3e5fe078ac8abbcc6eae995f994f65e7d5..877cc7bc4ba904cb4fc31fed54fe0b17c8a6a5b9 100644 (file)
@@ -17,6 +17,8 @@ Do not put spaces between # and the 'define'.
 #define ALT_CONFIG_PREFIX
 #define TRUSTED_CONFIG_LIST
 
+#define ALLOW_INSECURE_TAINTED_DATA
+
 #define APPENDFILE_MODE            0600
 #define APPENDFILE_DIRECTORY_MODE  0700
 #define APPENDFILE_LOCKFILE_MODE   0600
index 8a8a5fb67e19858e877b3959e22f1ea2ba406984..2f00dffb42d9c09c046b3ad8abb5818d522457ff 100644 (file)
@@ -640,11 +640,9 @@ after reading data. */
       : (flags) == O_RDWR ? "O_RDWR"   \
       : (flags) == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT" \
       : "??"); \
-  if (is_tainted(name) || is_tainted(dirname)) \
-    { \
-    log_write(0, LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted"); \
+  if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB file not permitted", name) \
+      || is_tainted2(dirname, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB directory not permitted", dirname)) \
     *dbpp = NULL; \
-    } \
   else \
     { EXIM_DBOPEN__(name, dirname, flags, mode, dbpp); } \
   DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", *dbpp); \
index 4e472ebe6bb8b34056033d577d56364a9f71a46e..f9f6746436ca0529ecb3194ee1b99dab85700670 100644 (file)
@@ -5550,10 +5550,11 @@ FILE * fp = NULL;
 if (!s || !*s)
   log_write(0, LOG_MAIN|LOG_PANIC,
     "Failed to expand %s: '%s'\n", varname, filename);
-else if (*s != '/' || is_tainted(s))
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "%s is not %s after expansion: '%s'\n",
-    varname, *s == '/' ? "untainted" : "absolute", s);
+else if (*s != '/')
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s is not absolute after expansion: '%s'\n",
+    varname, s);
+else if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted %s after expansion: '%s'\n", varname, s))
+  ;
 else if (!(fp = Ufopen(s, "rb")))
   log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
     "message texts: %s", s, reason, strerror(errno));
@@ -6163,9 +6164,10 @@ 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);
+          { uschar *m;
+         if ((m = is_tainted2(tmp, 0, "Tainted values '%s' " "for transport '%s' as a system filter", tmp, tpname)))
+            p->message = m;
+          }
           tpname = tmp;
           }
         else
index f54a781b7c22111016095251c23bf913977a8cd2..3273eb89b066a23f8ed35dbc7f7f0b3f4e5434f0 100644 (file)
@@ -44,8 +44,8 @@ uschar c = 1;
 struct stat statbuf;
 uschar * path;
 
-if (is_tainted(name)) 
-  { p = US"create"; path = US name; errno = ERRNO_TAINT; goto bad; }
+if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted path '%s' for new directory", name))
+  { p = US"create"; path = US name; errno = EACCES; goto bad; }
 
 if (parent)
   {
index 989e97b84bed7cfc9fe34442f51de71434c9ad40..4fb935528e49b1c6fe9ab3a959d0aa113bbc8336 100644 (file)
@@ -4483,13 +4483,13 @@ DEBUG(D_expand)
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
-if (is_tainted(string))
+{ uschar *m;
+if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s)))
   {
-  expand_string_message =
-    string_sprintf("attempt to expand tainted string '%s'", s);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+  expand_string_message = m;
   goto EXPAND_FAILED;
   }
+}
 
 while (*s)
   {
@@ -7645,10 +7645,12 @@ while (*s)
        /* Manually track tainting, as we deal in individual chars below */
 
        if (is_tainted(sub))
+          {
          if (yield->s && yield->ptr)
            gstring_rebuffer(yield);
          else
            yield->s = store_get(yield->size = Ustrlen(sub), TRUE);
+          }
 
        /* Check the UTF-8, byte-by-byte */
 
@@ -8209,6 +8211,7 @@ that is a bad idea, because expand_string_message is in dynamic store. */
 EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
+  {
   DEBUG(D_noutf8)
     {
     debug_printf_indent("|failed to expand: %s\n", string);
@@ -8228,6 +8231,7 @@ DEBUG(D_expand)
     if (f.expand_string_forcedfail)
       debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
     }
+  }
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;
index 4212c3328a113f342cc5f1090d03e8641f5a1c4c..6029ab4b1e258aaf52d2e3bd50e08a28fef40bf1 100644 (file)
@@ -1125,20 +1125,50 @@ if (f.running_in_test_harness && f.testsuite_delays) millisleep(millisec);
 
 /******************************************************************************/
 /* Taint-checked file opens */
+static inline uschar *
+is_tainted2(const void *p, int lflags, const char* fmt, ...)
+{
+va_list ap;
+uschar *msg;
+rmark mark;
+
+if (!is_tainted(p))
+  return NULL;
+
+mark = store_mark();
+va_start(ap, fmt);
+msg = string_from_gstring(string_vformat(NULL, SVFMT_TAINT_NOCHK|SVFMT_EXTEND, fmt, ap));
+va_end(ap);
+
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+if (allow_insecure_tainted_data)
+  {
+  if LOGGING(tainted) log_write(0, LOG_MAIN, "Warning: %s", msg);
+  store_reset(mark);
+  return NULL;
+  }
+#endif
+
+if (lflags) log_write(0, lflags, "%s", msg);
+return msg; /* no store_reset(), as the message might be used afterwards and Exim
+            is expected to exit anyway, so we do not care about the leaked
+            storage */
+}
 
 static inline int
 exim_open2(const char *pathname, int flags)
 {
-if (!is_tainted(pathname)) return open(pathname, flags);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
+  return open(pathname, flags);
 errno = EACCES;
 return -1;
 }
+
 static inline int
 exim_open(const char *pathname, int flags, mode_t mode)
 {
-if (!is_tainted(pathname)) return open(pathname, flags, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
+  return open(pathname, flags, mode);
 errno = EACCES;
 return -1;
 }
@@ -1146,16 +1176,16 @@ return -1;
 static inline int
 exim_openat(int dirfd, const char *pathname, int flags)
 {
-if (!is_tainted(pathname)) return openat(dirfd, pathname, flags);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
+  return openat(dirfd, pathname, flags);
 errno = EACCES;
 return -1;
 }
 static inline int
 exim_openat4(int dirfd, const char *pathname, int flags, mode_t mode)
 {
-if (!is_tainted(pathname)) return openat(dirfd, pathname, flags, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
+  return openat(dirfd, pathname, flags, mode);
 errno = EACCES;
 return -1;
 }
@@ -1164,8 +1194,8 @@ return -1;
 static inline FILE *
 exim_fopen(const char *pathname, const char *mode)
 {
-if (!is_tainted(pathname)) return fopen(pathname, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+if (!is_tainted2(pathname, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname))
+  return fopen(pathname, mode);
 errno = EACCES;
 return NULL;
 }
@@ -1173,8 +1203,8 @@ return NULL;
 static inline DIR *
 exim_opendir(const uschar * name)
 {
-if (!is_tainted(name)) return opendir(CCS name);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name);
+if (!is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name))
+  return opendir(CCS name);
 errno = EACCES;
 return NULL;
 }
index e96586048a1998d845b5b6d1c2502ccfe4b69b7c..9e68aaca81a065f0a178e9c63f5aad380fb894f2 100644 (file)
@@ -98,6 +98,10 @@ int     sqlite_lock_timeout    = 5;
 BOOL    move_frozen_messages   = FALSE;
 #endif
 
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+BOOL    allow_insecure_tainted_data = FALSE;
+#endif
+
 /* These variables are outside the #ifdef because it keeps the code less
 cluttered in several places (e.g. during logging) if we can always refer to
 them. Also, the tls_ variables are now always visible.  Note that these are
@@ -1055,6 +1059,9 @@ int     log_default[]          = { /* for initializing log_selector */
   Li_size_reject,
   Li_skip_delivery,
   Li_smtp_confirmation,
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+  Li_tainted,
+#endif
   Li_tls_certificate_verified,
   Li_tls_cipher,
   -1
@@ -1124,6 +1131,9 @@ bit_table log_options[]        = { /* must be in alphabetical order,
   BIT_TABLE(L, smtp_protocol_error),
   BIT_TABLE(L, smtp_syntax_error),
   BIT_TABLE(L, subject),
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+  BIT_TABLE(L, tainted),
+#endif
   BIT_TABLE(L, tls_certificate_verified),
   BIT_TABLE(L, tls_cipher),
   BIT_TABLE(L, tls_peerdn),
index 937cce776c43d2e484675a1745c0a9806fb0f99c..657e6c70631381cde5f14654db2598a1143304f8 100644 (file)
@@ -77,6 +77,10 @@ extern int     sqlite_lock_timeout;    /* Internal lock waiting timeout */
 extern BOOL    move_frozen_messages;   /* Get them out of the normal directory */
 #endif
 
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+extern BOOL    allow_insecure_tainted_data;
+#endif
+
 /* These variables are outside the #ifdef because it keeps the code less
 cluttered in several places (e.g. during logging) if we can always refer to
 them. Also, the tls_ variables are now always visible. */
index e22fc4fdf0eb72512190dfc95d45cdf02a8d1ab5..1b77f98fa235f9570a3030fee656a2253831d6de 100644 (file)
@@ -288,8 +288,11 @@ if (fd < 0 && errno == ENOENT)
   uschar *lastslash = Ustrrchr(name, '/');
   *lastslash = 0;
   created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
-  DEBUG(D_any) debug_printf("%s log directory %s\n",
-    created ? "created" : "failed to create", name);
+  DEBUG(D_any)
+    if (created)
+      debug_printf("created log directory %s\n", name);
+    else
+      debug_printf("failed to create log directory %s: %s\n", name, strerror(errno));
   *lastslash = '/';
   if (created) fd = Uopen(name, flags, LOG_MODE);
   }
@@ -452,7 +455,7 @@ return fd;
 it does not exist. This may be called recursively on failure, in order to open
 the panic log.
 
-The directory is in the static variable file_path. This is static so that it
+The directory is in the static variable file_path. This is static so that
 the work of sorting out the path is done just once per Exim process.
 
 Exim is normally configured to avoid running as root wherever possible, the log
@@ -487,62 +490,64 @@ people want, I hope. */
 
 ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]);
 
-/* Save the name of the mainlog for rollover processing. Without a datestamp,
-it gets statted to see if it has been cycled. With a datestamp, the datestamp
-will be compared. The static slot for saving it is the same size as buffer,
-and the text has been checked above to fit, so this use of strcpy() is OK. */
-
-if (type == lt_main)
+switch (type)
   {
-  Ustrcpy(mainlog_name, buffer);
-  if (string_datestamp_offset > 0)
-    mainlog_datestamp = mainlog_name + string_datestamp_offset;
-  }
+  case lt_main:
+    /* Save the name of the mainlog for rollover processing. Without a datestamp,
+    it gets statted to see if it has been cycled. With a datestamp, the datestamp
+    will be compared. The static slot for saving it is the same size as buffer,
+    and the text has been checked above to fit, so this use of strcpy() is OK. */
+
+    Ustrcpy(mainlog_name, buffer);
+    if (string_datestamp_offset > 0)
+      mainlog_datestamp = mainlog_name + string_datestamp_offset;
+    break;
 
-/* Ditto for the reject log */
+  case lt_reject:
+    /* Ditto for the reject log */
 
-else if (type == lt_reject)
-  {
-  Ustrcpy(rejectlog_name, buffer);
-  if (string_datestamp_offset > 0)
-    rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
-  }
+    Ustrcpy(rejectlog_name, buffer);
+    if (string_datestamp_offset > 0)
+      rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
+    break;
 
-/* and deal with the debug log (which keeps the datestamp, but does not
-update it) */
+  case lt_debug:
+    /* and deal with the debug log (which keeps the datestamp, but does not
+    update it) */
 
-else if (type == lt_debug)
-  {
-  Ustrcpy(debuglog_name, buffer);
-  if (tag)
-    {
-    /* this won't change the offset of the datestamp */
-    ok2 = string_format(buffer, sizeof(buffer), "%s%s",
-      debuglog_name, tag);
-    if (ok2)
-      Ustrcpy(debuglog_name, buffer);
-    }
-  }
+    Ustrcpy(debuglog_name, buffer);
+    if (tag)
+      {
+      /* this won't change the offset of the datestamp */
+      ok2 = string_format(buffer, sizeof(buffer), "%s%s",
+        debuglog_name, tag);
+      if (ok2)
+        Ustrcpy(debuglog_name, buffer);
+      }
+    break;
 
-/* Remove any datestamp if this is the panic log. This is rare, so there's no
-need to optimize getting the datestamp length. We remove one non-alphanumeric
-char afterwards if at the start, otherwise one before. */
+  default:
+    /* Remove any datestamp if this is the panic log. This is rare, so there's no
+  need to optimize getting the datestamp length. We remove one non-alphanumeric
+  char afterwards if at the start, otherwise one before. */
 
-else if (string_datestamp_offset >= 0)
-  {
-  uschar * from = buffer + string_datestamp_offset;
-  uschar * to = from + string_datestamp_length;
+    if (string_datestamp_offset >= 0)
+      {
+      uschar * from = buffer + string_datestamp_offset;
+      uschar * to = from + string_datestamp_length;
 
-  if (from == buffer || from[-1] == '/')
-    {
-    if (!isalnum(*to)) to++;
-    }
-  else
-    if (!isalnum(from[-1])) from--;
+      if (from == buffer || from[-1] == '/')
+        {
+        if (!isalnum(*to)) to++;
+        }
+      else
+        if (!isalnum(from[-1])) from--;
 
-  /* This copy is ok, because we know that to is a substring of from. But
-  due to overlap we must use memmove() not Ustrcpy(). */
-  memmove(from, to, Ustrlen(to)+1);
+      /* This copy is ok, because we know that to is a substring of from. But
+      due to overlap we must use memmove() not Ustrcpy(). */
+      memmove(from, to, Ustrlen(to)+1);
+      }
+    break;
   }
 
 /* If the file name is too long, it is an unrecoverable disaster */
@@ -556,9 +561,7 @@ if (!ok)
 *fd = log_open_as_exim(buffer);
 
 if (*fd >= 0)
-  {
   return;
-  }
 
 euid = geteuid();
 
@@ -710,26 +713,62 @@ return total_written;
 }
 
 
+/* Pull the file out of the configured or the compiled-in list.
+Called for an empty log_file_path element, for debug logging activation
+when file_path has not previously been set, and from the appenfile transport setup. */
 
-static void
-set_file_path(void)
+void
+set_file_path(BOOL *multiple)
 {
+uschar *s;
 int sep = ':';              /* Fixed separator - outside use */
-uschar *t;
-const uschar *tt = US LOG_FILE_PATH;
-while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
-  {
-  if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
-  file_path = string_copy(t);
-  break;
-  }
+const uschar *ss = *log_file_path ? log_file_path : US LOG_FILE_PATH;
+
+if (*ss)
+  for (logging_mode = 0;
+       s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE); )
+    {
+    if (Ustrcmp(s, "syslog") == 0)
+      logging_mode |= LOG_MODE_SYSLOG;
+    else if (!(logging_mode & LOG_MODE_FILE))  /* no file yet */
+      {
+      logging_mode |= LOG_MODE_FILE;
+      if (*s) file_path = string_copy(s);     /* If a non-empty path is given, use it */
+      }
+    else if (multiple) *multiple = TRUE;
+    }
+else
+  logging_mode = LOG_MODE_FILE;
+
+/* Set up the ultimate default if necessary. */
+
+if (logging_mode & LOG_MODE_FILE  &&  !*file_path)
+  if (LOG_FILE_PATH[0])
+    {
+      /* If we still do not have a file_path, we take
+      the first non-empty, non-syslog item in LOG_FILE_PATH, if there is
+      one.  If there is no such item, use the ultimate default in the
+      spool directory. */
+
+       for (ss = US LOG_FILE_PATH;
+            s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE);)
+          {
+            if (*s != '/') continue;
+            file_path = string_copy(s);
+          }
+    }
+  else file_path = string_sprintf("%s/log/%%slog", spool_directory);
 }
 
 
 void
 mainlog_close(void)
 {
-if (mainlogfd < 0) return;
+/* avoid closing it if it is closed already or if we do not see a chance
+to open the file mainlog later again */
+if (mainlogfd < 0 /* already closed */
+   || !(geteuid() == 0 || geteuid() == exim_uid))
+  return;
 (void)close(mainlogfd);
 mainlogfd = -1;
 mainlog_inode = 0;
@@ -841,41 +880,9 @@ if (!path_inspected)
 
   store_pool = POOL_PERM;
 
-  /* If nothing has been set, don't waste effort... the default values for the
-  statics are file_path="" and logging_mode = LOG_MODE_FILE. */
-
-  if (*log_file_path)
-    {
-    int sep = ':';              /* Fixed separator - outside use */
-    uschar *s;
-    const uschar *ss = log_file_path;
-
-    logging_mode = 0;
-    while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
-      {
-      if (Ustrcmp(s, "syslog") == 0)
-        logging_mode |= LOG_MODE_SYSLOG;
-      else if (logging_mode & LOG_MODE_FILE)
-       multiple = TRUE;
-      else
-        {
-        logging_mode |= LOG_MODE_FILE;
-
-        /* If a non-empty path is given, use it */
-
-        if (*s)
-          file_path = string_copy(s);
-
-        /* If the path is empty, we want to use the first non-empty, non-
-        syslog item in LOG_FILE_PATH, if there is one, since the value of
-        log_file_path may have been set at runtime. If there is no such item,
-        use the ultimate default in the spool directory. */
-
-        else
-         set_file_path();  /* Empty item in log_file_path */
-        }    /* First non-syslog item in log_file_path */
-      }      /* Scan of log_file_path */
-    }
+  /* make sure that we have a valid log file path in "file_path",
+  the open_log() later relies on it */
+  set_file_path(&multiple);
 
   /* If no modes have been selected, it is a major disaster */
 
@@ -883,11 +890,8 @@ if (!path_inspected)
     die(US"Neither syslog nor file logging set in log_file_path",
         US"Unexpected logging failure");
 
-  /* Set up the ultimate default if necessary. Then revert to the old store
-  pool, and record that we've sorted out the path. */
+  /* Revert to the old store pool, and record that we've sorted out the path. */
 
-  if (logging_mode & LOG_MODE_FILE  &&  !file_path[0])
-    file_path = string_sprintf("%s/log/%%slog", spool_directory);
   store_pool = old_pool;
   path_inspected = TRUE;
 
@@ -1241,6 +1245,7 @@ if (flags & LOG_PANIC)
 
   if (logging_mode & LOG_MODE_FILE)
     {
+    if (!*file_path) set_file_path(NULL);
     panic_recurseflag = TRUE;
     open_log(&paniclogfd, lt_panic, NULL);  /* Won't return on failure */
     panic_recurseflag = FALSE;
@@ -1493,7 +1498,7 @@ if (opts)
 resulting in certain setup not having been done.  Hack this for now so we
 do not segfault; note that nondefault log locations will not work */
 
-if (!*file_path) set_file_path();
+if (!*file_path) set_file_path(NULL);
 
 open_log(&fd, lt_debug, tag_name);
 
@@ -1515,5 +1520,14 @@ debug_file = NULL;
 unlink_log(lt_debug);
 }
 
+/* Called from the appendfile transport setup. */
+void
+open_logs(void)
+{
+set_file_path(NULL);
+if (!(logging_mode & LOG_MODE_FILE)) return;
+open_log(&mainlogfd, lt_main, 0);
+open_log(&rejectlogfd, lt_reject, 0);
+}
 
 /* End of log.c */
index ad1df29d10b4c3a775fa6683c328a2f136233787..38b7c2ad345c4bde4f6cbcadb74ae42994eb2f8a 100644 (file)
@@ -102,11 +102,13 @@ if (Ustrncmp(query, "servers", 7) == 0)
         }
       }
 
-    if (is_tainted(server))
-      {
-      *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
+    { uschar *m;
+    if ((m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server)))
+     {
+      *errmsg = m;
       return DEFER;
       }
+    }
 
     rc = (*fn)(ss+1, server, result, errmsg, &defer_break, do_cache, opts);
     if (rc != DEFER || defer_break) return rc;
@@ -158,11 +160,13 @@ else
        server = ele;
        }
 
-      if (is_tainted(server))
+      { uschar *m;
+      if ((m = is_tainted2(server, 0, "Tainted %s server '%s'", name, server)))
         {
-        *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
+        *errmsg = m;
         return DEFER;
         }
+      }
 
       rc = (*fn)(query, server, result, errmsg, &defer_break, do_cache, opts);
       if (rc != DEFER || defer_break) return rc;
index f8987d60455f43febeec18eeae0e139e9ac549b9..ccdcc451f703a68e88af3ffb9af4747b8f409f2d 100644 (file)
@@ -491,6 +491,9 @@ enum logbit {
   Li_smtp_mailauth,
   Li_smtp_no_mail,
   Li_subject,
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+  Li_tainted,
+#endif
   Li_tls_certificate_verified,
   Li_tls_cipher,
   Li_tls_peerdn,
index fa339520624f51bb091be5a8cb216b556c669f40..896d00f30bdfe41b74eb1df5f3d77f734e792030 100644 (file)
@@ -1415,12 +1415,8 @@ for (;;)
       return FF_ERROR;
       }
 
-    if (is_tainted(filename))
-      {
-      *error = string_sprintf("Tainted name '%s' for included file  not permitted\n",
-       filename);
+    if ((*error = is_tainted2(filename, 0, "Tainted name '%s' for included file not permitted\n", filename)))
       return FF_ERROR;
-      }
 
     /* Check file name if required */
 
index fb3714ea21bc2e35cd507d42e35f4a0761620fd1..a12e5de2974719ac9e9cd48fb00a4d01e25a06c4 100644 (file)
@@ -179,10 +179,8 @@ struct stat statbuf;
 /* Reading a file is a form of expansion; we wish to deny attackers the
 capability to specify the file name. */
 
-if (is_tainted(filename))
+if ((*error = is_tainted2(filename, 0, "Tainted name '%s' for file read not permitted\n", filename)))
   {
-  *error = string_sprintf("Tainted name '%s' for file read not permitted\n",
-                       filename);
   *yield = FF_ERROR;
   return NULL;
   }
index ae36fa0c5601db9e2872b91f56dcc7425775d2ca..34ebf87690777ce927f7a0ff37bc6f5e3c77e09d 100644 (file)
@@ -68,6 +68,9 @@ static optionlist optionlist_config[] = {
   { "add_environment",          opt_stringptr,   {&add_environment} },
   { "admin_groups",             opt_gidlist,     {&admin_groups} },
   { "allow_domain_literals",    opt_bool,        {&allow_domain_literals} },
+#ifdef ALLOW_INSECURE_TAINTED_DATA
+  { "allow_insecure_tainted_data", opt_bool,     {&allow_insecure_tainted_data} },
+#endif
   { "allow_mx_to_ip",           opt_bool,        {&allow_mx_to_ip} },
   { "allow_utf8_domains",       opt_bool,        {&allow_utf8_domains} },
   { "auth_advertise_hosts",     opt_stringptr,   {&auth_advertise_hosts} },
index 4a43818ff45a745b42c000976888e2d3fc289828..32bde9ec3eeef7a7f9ad7e37e581cc324eb2991f 100644 (file)
@@ -66,10 +66,8 @@ if (expandable)
       "\"%s\" in %s router: %s", tpname, router_name, expand_string_message);
     return FALSE;
     }
-  if (is_tainted(ss))
+  if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted tainted value '%s' from '%s' for transport", ss, tpname))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "attempt to use tainted value '%s' from '%s' for transport", ss, tpname);
     addr->basic_errno = ERRNO_BADTRANSPORT;
     /* Avoid leaking info to an attacker */
     addr->message = US"internal configuration error";
index 421d9c789c0ef08d06c29079bf39c61990c533c8..81d1d20f982651c6042b7485eb5256cb69d94fbb 100644 (file)
@@ -392,12 +392,8 @@ lookup_info *lk = lookup_list[search_type];
 uschar keybuffer[256];
 int old_pool = store_pool;
 
-if (filename && is_tainted(filename))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "Tainted filename for search: '%s'", filename);
+if (filename && is_tainted2(filename, LOG_MAIN|LOG_PANIC, "Tainted filename for search: '%s'", filename))
   return NULL;
-  }
 
 /* Change to the search store pool and remember our reset point */
 
@@ -714,7 +710,7 @@ if (opts)
 /* Arrange to put this database at the top of the LRU chain if it is a type
 that opens real files. */
 
-if (  open_top != (tree_node *)handle 
+if (  open_top != (tree_node *)handle
    && lookup_list[t->name[0]-'0']->type == lookup_absfile)
   {
   search_cache *c = (search_cache *)(t->data.ptr);
index f103c2752ebd76c23dbec4e704ea6be2e4525a78..4e8c4486944476610c96e60bd96b05362c8f4795 100644 (file)
@@ -53,11 +53,8 @@ if (!(expint = expand_string(istring)))
   return FALSE;
   }
 
-if (is_tainted(expint))
+if (is_tainted2(expint, LOG_MAIN|LOG_PANIC, "Tainted value '%s' from '%s' for interface", expint, istring))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "attempt to use tainted value '%s' from '%s' for interface",
-    expint, istring);
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"interface\" "
       "option for %s: configuration error", msg);
@@ -472,7 +469,7 @@ if (ob->socks_proxy)
   {
   int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
                                sc->tblock, ob->connect_timeout);
-  
+
   if (sock >= 0)
     {
     if (early_data && early_data->data && early_data->len)
index 139f9a3ef95b67f8790cb76d40277cfc1fe9c298..5d957b62e0b833358abc6ec608a496d2df3a5b1c 100644 (file)
@@ -174,6 +174,9 @@ Arguments:
 Returns:     OK, FAIL, or DEFER
 */
 
+void
+open_logs(void);
+
 static int
 appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
   transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
@@ -183,6 +186,14 @@ appendfile_transport_options_block *ob =
 uschar *q = ob->quota;
 double default_value = 0.0;
 
+addrlist = addrlist;    /* Keep picky compilers happy */
+dummy = dummy;
+uid = uid;
+gid = gid;
+
+/* we can't wait until we're not privileged anymore */
+open_logs();
+
 if (ob->expand_maildir_use_size_file)
        ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
                US"`maildir_use_size_file` in transport", tblock->name);
index 865abbf4f555b73b005817027da9a001fc557be2..80c7c0db0c655306709facc766c9070b5dabadcf 100644 (file)
@@ -404,14 +404,15 @@ recipient cache. */
 
 if (oncelog && *oncelog && to)
   {
+  uschar *m;
   time_t then = 0;
 
-  if (is_tainted(oncelog))
+  if ((m = is_tainted2(oncelog, 0, "Tainted '%s' (once file for %s transport)"
+      " not permitted", oncelog, tblock->name)))
     {
     addr->transport_return = DEFER;
     addr->basic_errno = EACCES;
-    addr->message = string_sprintf("Tainted '%s' (once file for %s transport)"
-      " not permitted", oncelog, tblock->name);
+    addr->message = m;
     goto END_OFF;
     }
 
@@ -515,13 +516,14 @@ if (oncelog && *oncelog && to)
 
   if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
     {
+    uschar *m;
     int log_fd;
-    if (is_tainted(logfile))
+    if ((m = is_tainted2(logfile, 0, "Tainted '%s' (logfile for %s transport)"
+       " not permitted", logfile, tblock->name)))
       {
       addr->transport_return = DEFER;
       addr->basic_errno = EACCES;
-      addr->message = string_sprintf("Tainted '%s' (logfile for %s transport)"
-       " not permitted", logfile, tblock->name);
+      addr->message = m;
       goto END_OFF;
       }
 
@@ -548,12 +550,13 @@ if (oncelog && *oncelog && to)
 /* We are going to send a message. Ensure any requested file is available. */
 if (file)
   {
-  if (is_tainted(file))
+  uschar *m;
+  if ((m = is_tainted2(file, 0, "Tainted '%s' (file for %s transport)"
+      " not permitted", file, tblock->name)))
     {
     addr->transport_return = DEFER;
     addr->basic_errno = EACCES;
-    addr->message = string_sprintf("Tainted '%s' (file for %s transport)"
-      " not permitted", file, tblock->name);
+    addr->message = m;
     return FALSE;
     }
   if (!(ff = Ufopen(file, "rb")) && !ob->file_optional)
index 1cb574ee72c791a4e98b2699774b930e9b18fd62..da49307b1c6cd25e7783a2f7a8bb8c2476fca31a 100644 (file)
@@ -592,13 +592,16 @@ if (!cmd || !*cmd)
     tblock->name);
   return FALSE;
   }
-if (is_tainted(cmd))
+
+{ uschar *m;
+if ((m = is_tainted2(cmd, 0, "Tainted '%s' (command "
+    "for %s transport) not permitted", cmd, tblock->name)))
   {
-  addr->message = string_sprintf("Tainted '%s' (command "
-    "for %s transport) not permitted", cmd, tblock->name);
   addr->transport_return = PANIC;
+  addr->message = m;
   return FALSE;
   }
+}
 
 /* When a pipe is set up by a filter file, there may be values for $thisaddress
 and numerical the variables in existence. These are passed in
index 264ebc0946a9bff05d999d1341ee81c71d003bb8..e2b2250adcb6b392c1fded04489f5bd8c64452be 100644 (file)
@@ -5092,11 +5092,8 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     else
       if (ob->hosts_randomize) s = expanded_hosts = string_copy(s);
 
-    if (is_tainted(s))
+    if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted host list '%s' from '%s' in transport %s", s, ob->hosts, tblock->name))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-       "attempt to use tainted host list '%s' from '%s' in transport %s",
-       s, ob->hosts, tblock->name);
       /* Avoid leaking info to an attacker */
       addrlist->message = US"internal configuration error";
       addrlist->transport_return = PANIC;
diff --git a/test/aux-fixed/0990/example.com b/test/aux-fixed/0990/example.com
new file mode 100644 (file)
index 0000000..b586074
--- /dev/null
@@ -0,0 +1 @@
+hans
diff --git a/test/confs/0990 b/test/confs/0990
new file mode 100644 (file)
index 0000000..076a399
--- /dev/null
@@ -0,0 +1,2 @@
+# this is the default
+allow_insecure_tainted_data = ALLOW_TAINTED
index 5331ae48f92280de1e7a5302b646b687a230417a..d0130a6a971a5210d70f5ac7a2e48a5257856c82 100644 (file)
@@ -35,7 +35,7 @@
 2017-07-30 18:51:05.712 10HmaZ-0005vi-00 Completed
 2017-07-30 18:51:05.712 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for f@test.ex
 2017-07-30 18:51:05.712 10HmbA-0005vi-00 ** f@test.ex: Unrouteable address
-2017-07-30 18:51:05.712 10HmbA-0005vi-00 bounce_message_file is not untainted after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
 
 2017-07-30 18:51:05.712 10HmbJ-0005vi-00 <= <> R=10HmbA-0005vi-00 U=EXIMUSER P=local S=sss for CALLER@myhost.test.ex
 2017-07-30 18:51:05.712 10HmbJ-0005vi-00 => CALLER <CALLER@myhost.test.ex> R=bounces T=savebounce
index 0cf96cfdc17432f1565b7e22bffd925422d41820..aa9fc2b4826983b68591f306da9a575c804ad87f 100644 (file)
@@ -3,6 +3,6 @@
 
 2017-07-30 18:51:05.712 10HmaZ-0005vi-00 Failed to expand bounce_message_file: '$acl_m_unset'
 
-2017-07-30 18:51:05.712 10HmbA-0005vi-00 bounce_message_file is not untainted after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
 
 2017-07-30 18:51:05.712 10HmbB-0005vi-00 Failed to open TESTSUITE/aux-fixed/0608.nonexist.tmpl for warning message texts: No such file or directory
diff --git a/test/scripts/0990-Allow-Tainted-Data/0990 b/test/scripts/0990-Allow-Tainted-Data/0990
new file mode 100644 (file)
index 0000000..87cf4c0
--- /dev/null
@@ -0,0 +1,7 @@
+# Allow insecure tainted data
+exim -DALLOW_TAINTED=no -f hans@example.com -be 
+${lookup{$sender_address_local_part}lsearch{DIR/aux-fixed/0990/$sender_address_domain}{yes}{no}}
+****
+exim -DALLOW_TAINTED=yes -f hans@example.com -be 
+${lookup{$sender_address_local_part}lsearch{DIR/aux-fixed/0990/$sender_address_domain}{yes}{no}}
+****
diff --git a/test/scripts/0990-Allow-Tainted-Data/REQUIRES b/test/scripts/0990-Allow-Tainted-Data/REQUIRES
new file mode 100644 (file)
index 0000000..5fabbde
--- /dev/null
@@ -0,0 +1 @@
+feature _OPT_MAIN_ALLOW_INSECURE_TAINTED_DATA
index 0cf96cfdc17432f1565b7e22bffd925422d41820..aa9fc2b4826983b68591f306da9a575c804ad87f 100644 (file)
@@ -3,6 +3,6 @@
 
 2017-07-30 18:51:05.712 10HmaZ-0005vi-00 Failed to expand bounce_message_file: '$acl_m_unset'
 
-2017-07-30 18:51:05.712 10HmbA-0005vi-00 bounce_message_file is not untainted after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0608.CALLER@myhost.test.ex'
 
 2017-07-30 18:51:05.712 10HmbB-0005vi-00 Failed to open TESTSUITE/aux-fixed/0608.nonexist.tmpl for warning message texts: No such file or directory
diff --git a/test/stderr/0990 b/test/stderr/0990
new file mode 100644 (file)
index 0000000..3feb126
--- /dev/null
@@ -0,0 +1,3 @@
+1999-03-02 09:44:33 Tainted filename for search: 'TESTSUITE/aux-fixed/0990/example.com'
+1999-03-02 09:44:33 Warning: Tainted filename for search: 'TESTSUITE/aux-fixed/0990/example.com'
+1999-03-02 09:44:33 Warning: Tainted filename 'TESTSUITE/aux-fixed/0990/example.com'
diff --git a/test/stdout/0990 b/test/stdout/0990
new file mode 100644 (file)
index 0000000..5a4290a
--- /dev/null
@@ -0,0 +1,4 @@
+> Failed: (null)
+> 
+> yes
+>