summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt45
-rw-r--r--doc/doc-txt/NewStuff45
-rw-r--r--src/src/EDITME7
-rw-r--r--src/src/acl.c30
-rw-r--r--src/src/config.h.defaults2
-rw-r--r--src/src/dbstuff.h6
-rw-r--r--src/src/deliver.c16
-rw-r--r--src/src/directory.c5
-rw-r--r--src/src/expand.c12
-rw-r--r--src/src/functions.h54
-rw-r--r--src/src/globals.c10
-rw-r--r--src/src/globals.h4
-rw-r--r--src/src/log.c179
-rw-r--r--src/src/lookups/lf_sqlperform.c14
-rw-r--r--src/src/macros.h3
-rw-r--r--src/src/parse.c6
-rw-r--r--src/src/rda.c4
-rw-r--r--src/src/readconf.c3
-rw-r--r--src/src/routers/rf_get_transport.c4
-rw-r--r--src/src/search.c8
-rw-r--r--src/src/smtp_out.c7
-rw-r--r--src/src/transports/appendfile.c14
-rw-r--r--src/src/transports/autoreply.c21
-rw-r--r--src/src/transports/pipe.c9
-rw-r--r--src/src/transports/smtp.c5
-rw-r--r--test/aux-fixed/0990/example.com1
-rw-r--r--test/confs/09902
-rw-r--r--test/log/06222
-rw-r--r--test/log/45204
-rw-r--r--test/paniclog/06222
-rw-r--r--test/scripts/0990-Allow-Tainted-Data/09907
-rw-r--r--test/scripts/0990-Allow-Tainted-Data/REQUIRES1
-rw-r--r--test/stderr/06222
-rw-r--r--test/stderr/09903
-rw-r--r--test/stdout/09904
35 files changed, 353 insertions, 188 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 9c66f5cc9..9812f219d 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -231,6 +231,14 @@
<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>
</indexterm>
@@ -9476,7 +9484,22 @@ reasons,
.cindex "tainted data" expansion
.cindex expansion "tainted data"
and expansion of data deriving from the sender (&"tainted data"&)
-is not permitted.
+.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
+Common ways of obtaining untainted equivalents of variables with
+tainted values
+.cindex "tainted data" "de-tainting"
+come down to using the tainted value as a lookup key in a trusted database.
+This database could be the filesystem structure,
+or the password file,
+or accessed via a DBMS.
+Specific methods are indexed under &"de-tainting"&.
+.wen
@@ -14412,6 +14435,8 @@ 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"
@@ -15019,6 +15044,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"
@@ -38178,6 +38215,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
@@ -38571,6 +38609,11 @@ using a CA trust anchor,
&`CA=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
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 16dec8808..ba697c2fa 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -6,6 +6,51 @@ Before a formal release, there may be quite a lot of detail so that people can
test from the snapshots or the Git before the documentation is updated. Once
the documentation is updated, this file is reduced to a short list.
+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
------------
diff --git a/src/src/EDITME b/src/src/EDITME
index 8da36a353..cebb8e2ec 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -749,6 +749,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
diff --git a/src/src/acl.c b/src/src/acl.c
index 7061230b4..bdc2b351d 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3598,20 +3598,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);
@@ -4007,10 +4009,8 @@ if (Ustrchr(ss, ' ') == NULL)
else if (*ss == '/')
{
struct stat statbuf;
- if (is_tainted(ss))
+ if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted ACL file name '%s'", 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;
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index e17f015f9..4e8b18904 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -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
diff --git a/src/src/dbstuff.h b/src/src/dbstuff.h
index c1fb54346..dcee78696 100644
--- a/src/src/dbstuff.h
+++ b/src/src/dbstuff.h
@@ -643,11 +643,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); \
diff --git a/src/src/deliver.c b/src/src/deliver.c
index f5f065e63..4d5b12bde 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -5538,10 +5538,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));
@@ -6151,9 +6152,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
diff --git a/src/src/directory.c b/src/src/directory.c
index 2d4d565f4..ece1ee8f3 100644
--- a/src/src/directory.c
+++ b/src/src/directory.c
@@ -44,6 +44,11 @@ uschar c = 1;
struct stat statbuf;
uschar * path;
+/* does not work with 4.94
+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)
{
path = string_sprintf("%s%s%s", parent, US"/", name);
diff --git a/src/src/expand.c b/src/src/expand.c
index 05de94c49..dc4b4e102 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -4383,13 +4383,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 != 0)
{
@@ -7629,10 +7629,12 @@ while (*s != 0)
/* 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 */
@@ -8193,6 +8195,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);
@@ -8212,6 +8215,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;
diff --git a/src/src/functions.h b/src/src/functions.h
index e22fd4f99..b4b2e3293 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -1084,36 +1084,66 @@ 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;
}
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;
}
@@ -1121,8 +1151,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;
}
@@ -1130,8 +1160,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;
}
diff --git a/src/src/globals.c b/src/src/globals.c
index fcb9cc0b5..5e42f5b90 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -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
@@ -1034,6 +1038,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
@@ -1101,6 +1108,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),
diff --git a/src/src/globals.h b/src/src/globals.h
index bb811553c..e0ca348ff 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -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. */
diff --git a/src/src/log.c b/src/src/log.c
index 5d36b4983..2108ed46f 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -492,60 +492,53 @@ 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 && string_datestamp_offset >= 0)
- {
- Ustrcpy(mainlog_name, buffer);
- mainlog_datestamp = mainlog_name + string_datestamp_offset;
- }
-
-/* Ditto for the reject log */
-
-else if (type == lt_reject && string_datestamp_offset >= 0)
- {
- Ustrcpy(rejectlog_name, buffer);
- rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
- }
-
-/* 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);
- }
- }
-
-/* 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)
+switch (type)
{
- uschar * from = buffer + string_datestamp_offset;
- uschar * to = from + string_datestamp_length;
+ 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;
+ case lt_reject:
+ /* Ditto for the reject log */
+ Ustrcpy(rejectlog_name, buffer);
+ if (string_datestamp_offset > 0)
+ rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
+ case lt_debug:
+ /* and deal with the debug log (which keeps the datestamp, but does not
+ update it) */
+ 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);
+ }
+ 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. */
+ 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);
+ }
}
/* If the file name is too long, it is an unrecoverable disaster */
@@ -713,18 +706,36 @@ return total_written;
}
-
-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)))
+const uschar *ss = *log_file_path ? log_file_path : US LOG_FILE_PATH;
+
+logging_mode = 0;
+while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
{
- if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
- file_path = string_copy(t);
- break;
+ if (Ustrcmp(s, "syslog") == 0)
+ logging_mode |= LOG_MODE_SYSLOG;
+ else if (logging_mode & LOG_MODE_FILE) /* we know a file already */
+ {
+ if (multiple) *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. */
+ }
}
}
@@ -732,7 +743,11 @@ while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
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;
@@ -847,38 +862,7 @@ if (!path_inspected)
/* 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 */
- }
+ if (*log_file_path) set_file_path(&multiple);
/* If no modes have been selected, it is a major disaster */
@@ -1499,7 +1483,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);
@@ -1521,5 +1505,12 @@ debug_file = NULL;
unlink_log(lt_debug);
}
+void
+open_logs(void)
+{
+set_file_path(NULL);
+open_log(&mainlogfd, lt_main, 0);
+open_log(&rejectlogfd, lt_reject, 0);
+}
/* End of log.c */
diff --git a/src/src/lookups/lf_sqlperform.c b/src/src/lookups/lf_sqlperform.c
index ad1df29d1..38b7c2ad3 100644
--- a/src/src/lookups/lf_sqlperform.c
+++ b/src/src/lookups/lf_sqlperform.c
@@ -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;
diff --git a/src/src/macros.h b/src/src/macros.h
index b2f86ed53..aeaaeb736 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -497,6 +497,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,
diff --git a/src/src/parse.c b/src/src/parse.c
index 086b010c3..bf780998f 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -1410,12 +1410,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 */
diff --git a/src/src/rda.c b/src/src/rda.c
index ce6e7a36d..d2a8eb310 100644
--- a/src/src/rda.c
+++ b/src/src/rda.c
@@ -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;
}
diff --git a/src/src/readconf.c b/src/src/readconf.c
index f962f9029..694a5bbdb 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -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} },
diff --git a/src/src/routers/rf_get_transport.c b/src/src/routers/rf_get_transport.c
index 4a43818ff..32bde9ec3 100644
--- a/src/src/routers/rf_get_transport.c
+++ b/src/src/routers/rf_get_transport.c
@@ -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";
diff --git a/src/src/search.c b/src/src/search.c
index f8aaacb04..f6e4d1f5b 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -343,12 +343,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 */
@@ -639,7 +635,7 @@ DEBUG(D_lookup)
/* 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);
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index d1f69024e..ade098c9e 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -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);
@@ -425,7 +422,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)
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 8ab8b6016..c0f4de4c8 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -217,6 +217,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)
@@ -231,6 +234,9 @@ 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);
@@ -1286,12 +1292,14 @@ if (!(path = expand_string(fdname)))
expand_string_message);
goto ret_panic;
}
-if (is_tainted(path))
+{ uschar *m;
+if ((m = is_tainted2(path, 0, "Tainted '%s' (file or directory "
+ "name for %s transport) not permitted", path, tblock->name)))
{
- addr->message = string_sprintf("Tainted '%s' (file or directory "
- "name for %s transport) not permitted", path, tblock->name);
+ addr->message = m;
goto ret_panic;
}
+}
if (path[0] != '/')
{
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 865abbf4f..80c7c0db0 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -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)
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 27422bd42..fc44fa585 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -599,13 +599,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
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 9ee6a578a..64ca788b0 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -4715,11 +4715,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
index 000000000..b5860744c
--- /dev/null
+++ b/test/aux-fixed/0990/example.com
@@ -0,0 +1 @@
+hans
diff --git a/test/confs/0990 b/test/confs/0990
new file mode 100644
index 000000000..076a39914
--- /dev/null
+++ b/test/confs/0990
@@ -0,0 +1,2 @@
+# this is the default
+allow_insecure_tainted_data = ALLOW_TAINTED
diff --git a/test/log/0622 b/test/log/0622
index c5a9ef29b..36c161ef1 100644
--- a/test/log/0622
+++ b/test/log/0622
@@ -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/0622.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0622.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
diff --git a/test/log/4520 b/test/log/4520
index d1797e68c..234624cc0 100644
--- a/test/log/4520
+++ b/test/log/4520
@@ -22,8 +22,8 @@
1999-03-02 09:44:33 10HmaX-0005vi-00 <= <> U=CALLER P=local S=sss for e0@test.ex
1999-03-02 09:44:33 10HmaX-0005vi-00 failed to expand dkim_timestamps: unknown variable in "${bogus}"
1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: message could not be signed, and dkim_strict is set. Deferring message delivery.
-1999-03-02 09:44:33 10HmaX-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: send() to ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] failed: failed to expand dkim_timestamps: unknown variable in "${bogus}": No such file or directory
-1999-03-02 09:44:33 10HmaX-0005vi-00 == e0@test.ex R=client T=send_to_server defer (EEE): No such file or directory H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: send() to ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] failed: failed to expand dkim_timestamps: unknown variable in "${bogus}"
+1999-03-02 09:44:33 10HmaX-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: send() to ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] failed: failed to expand dkim_timestamps: unknown variable in "${bogus}": Permission denied
+1999-03-02 09:44:33 10HmaX-0005vi-00 == e0@test.ex R=client T=send_to_server defer (EEE): Permission denied H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: send() to ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] failed: failed to expand dkim_timestamps: unknown variable in "${bogus}"
1999-03-02 09:44:33 10HmaX-0005vi-00 ** e0@test.ex: retry timeout exceeded
1999-03-02 09:44:33 10HmaX-0005vi-00 e0@test.ex: error ignored
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
diff --git a/test/paniclog/0622 b/test/paniclog/0622
index 544d65c43..773b0cea7 100644
--- a/test/paniclog/0622
+++ b/test/paniclog/0622
@@ -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/0622.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0622.CALLER@myhost.test.ex'
2017-07-30 18:51:05.712 10HmbB-0005vi-00 Failed to open TESTSUITE/aux-fixed/0622.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
index 000000000..87cf4c009
--- /dev/null
+++ b/test/scripts/0990-Allow-Tainted-Data/0990
@@ -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
index 000000000..5fabbded0
--- /dev/null
+++ b/test/scripts/0990-Allow-Tainted-Data/REQUIRES
@@ -0,0 +1 @@
+feature _OPT_MAIN_ALLOW_INSECURE_TAINTED_DATA
diff --git a/test/stderr/0622 b/test/stderr/0622
index 544d65c43..773b0cea7 100644
--- a/test/stderr/0622
+++ b/test/stderr/0622
@@ -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/0622.CALLER@myhost.test.ex'
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Tainted bounce_message_file after expansion: 'TESTSUITE/aux-fixed/0622.CALLER@myhost.test.ex'
2017-07-30 18:51:05.712 10HmbB-0005vi-00 Failed to open TESTSUITE/aux-fixed/0622.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
index 000000000..88317703b
--- /dev/null
+++ b/test/stderr/0990
@@ -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
index 000000000..5a4290afd
--- /dev/null
+++ b/test/stdout/0990
@@ -0,0 +1,4 @@
+> Failed: (null)
+>
+> yes
+>