diff options
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 +> |