From 557870d3828bf4a99f542019f04df3fbadd85c47f4bbace84de71d47b6af5225 Mon Sep 17 00:00:00 2001 From: Marcus Rueckert Date: Tue, 25 Apr 2017 09:33:40 +0000 Subject: [PATCH 1/3] Accepting request 490905 from home:pwcau:branches:server:mail Corrections for inverted bcond import exim-4_86_2+fixes branch + fix CVE-2016-1531 when installed setuid root, allows local users to gain privileges via the perl_startup argument. + fix Bug 1805: store the initial working directory, expand $initial_cwd + fix Bug 1671: segfault after delivery (https://bugs.exim.org/show_bug.cgi?id=1671) + Don't issue env warning if env is empty - fix CVE-2016-9963: DKIM information leakage - conditionally disable DANE on SuSE versions with OpenSSL < 1.0 - disable i18n by default, utf8_downconvert seems to cause crashes OBS-URL: https://build.opensuse.org/request/show/490905 OBS-URL: https://build.opensuse.org/package/show/server:mail/exim?expand=0&rev=183 --- ...8fe25dbfb1e31493488ad695bde55b890397.patch | 1425 +++++++++++++++++ exim.changes | 15 + exim.spec | 28 +- ...2defdc5118834e801d4fe8f11c1d9b5ebadf.patch | 66 + 4 files changed, 1528 insertions(+), 6 deletions(-) create mode 100644 exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch create mode 100644 fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch diff --git a/exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch b/exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch new file mode 100644 index 0000000..d72f55c --- /dev/null +++ b/exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch @@ -0,0 +1,1425 @@ +diff -ru a/daemon.c b/daemon.c +--- a/src/daemon.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/daemon.c 2017-04-24 09:33:17.356999655 +0200 +@@ -735,6 +735,7 @@ + /* Release any store used in this process, including the store used for holding + the incoming host address and an expanded active_hostname. */ + ++log_close_all(); + store_reset(reset_point); + sender_host_address = NULL; + } +diff -ru a/deliver.c b/deliver.c +--- a/src/deliver.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/deliver.c 2017-04-24 09:33:17.356999655 +0200 +@@ -9,6 +9,7 @@ + + + #include "exim.h" ++#include + + + /* Data block for keeping track of subprocesses for parallel remote +@@ -7904,17 +7905,36 @@ + uschar * + deliver_get_sender_address (uschar * id) + { ++int rc; ++uschar * new_sender_address, ++ * save_sender_address; ++ + if (!spool_open_datafile(id)) + return NULL; + ++/* Save and restore the global sender_address. I'm not sure if we should ++not save/restore all the other global variables too, because ++spool_read_header() may change all of them. But OTOH, when this ++deliver_get_sender_address() gets called, the current message is done ++already and nobody needs the globals anymore. (HS12, 2015-08-21) */ ++ + sprintf(CS spoolname, "%s-H", id); +-if (spool_read_header(spoolname, TRUE, TRUE) != spool_read_OK) ++save_sender_address = sender_address; ++ ++rc = spool_read_header(spoolname, TRUE, TRUE); ++ ++new_sender_address = sender_address; ++sender_address = save_sender_address; ++ ++if (rc != spool_read_OK) + return NULL; + ++assert(new_sender_address); ++ + (void)close(deliver_datafile); + deliver_datafile = -1; + +-return sender_address; ++return new_sender_address; + } + + /* vi: aw ai sw=2 +diff -ru a/dns.c b/dns.c +--- a/src/dns.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/dns.c 2017-04-24 09:33:17.356999655 +0200 +@@ -390,7 +390,8 @@ + + dnss->aptr += namelen; + GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */ +-dnss->aptr += 6; /* Don't want class or TTL */ ++dnss->aptr += 2; /* Don't want class */ ++GETLONG(dnss->srr.ttl, dnss->aptr); /* TTL */ + GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */ + dnss->srr.data = dnss->aptr; /* The record's data follows */ + dnss->aptr += dnss->srr.size; /* Advance to next RR */ +diff -ru a/exim.c b/exim.c +--- a/src/exim.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/exim.c 2017-04-24 09:33:17.360999706 +0200 +@@ -3730,6 +3730,13 @@ + exit(EXIT_FAILURE); + } + ++/* Store the initial cwd before we change directories */ ++if ((initial_cwd = getcwd(NULL, 0)) == NULL) ++ { ++ perror("exim: can't get the current working directory"); ++ exit(EXIT_FAILURE); ++ } ++ + readconf_main(); + + if (cleanup_environment() == FALSE) +@@ -4017,9 +4024,10 @@ + { + int i; + uschar *p = big_buffer; +- char * dummy; + Ustrcpy(p, "cwd= (failed)"); +- dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4); ++ ++ Ustrncpy(p + 4, initial_cwd, big_buffer_size-5); ++ + while (*p) p++; + (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc); + while (*p) p++; +diff -ru a/expand.c b/expand.c +--- a/src/expand.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/expand.c 2017-04-24 09:33:17.360999706 +0200 +@@ -533,6 +533,7 @@ + { "host_lookup_deferred",vtype_int, &host_lookup_deferred }, + { "host_lookup_failed", vtype_int, &host_lookup_failed }, + { "host_port", vtype_int, &deliver_host_port }, ++ { "initial_cwd", vtype_stringptr, &initial_cwd }, + { "inode", vtype_ino, &deliver_inode }, + { "interface_address", vtype_stringptr, &interface_address }, + { "interface_port", vtype_int, &interface_port }, +diff -ru a/functions.h b/functions.h +--- a/src/functions.h 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/functions.h 2017-04-24 09:33:17.360999706 +0200 +@@ -375,7 +375,7 @@ + extern int smtp_feof(void); + extern int smtp_ferror(void); + extern uschar *smtp_get_connection_info(void); +-extern BOOL smtp_get_interface(uschar *, int, address_item *, BOOL *, ++extern BOOL smtp_get_interface(uschar *, int, address_item *, + uschar **, uschar *); + extern BOOL smtp_get_port(uschar *, address_item *, int *, uschar *); + extern int smtp_getc(void); +diff -ru a/globals.c b/globals.c +--- a/src/globals.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/globals.c 2017-04-24 09:33:17.360999706 +0200 +@@ -783,6 +783,7 @@ + uschar *ignore_fromline_hosts = NULL; + BOOL inetd_wait_mode = FALSE; + int inetd_wait_timeout = -1; ++uschar *initial_cwd = NULL; + uschar *interface_address = NULL; + int interface_port = -1; + BOOL is_inetd = FALSE; +diff -ru a/globals.h b/globals.h +--- a/src/globals.h 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/globals.h 2017-04-24 09:33:17.360999706 +0200 +@@ -508,6 +508,7 @@ + extern uschar *ignore_fromline_hosts; /* Hosts permitted to send "From " */ + extern BOOL inetd_wait_mode; /* Whether running in inetd wait mode */ + extern int inetd_wait_timeout; /* Timeout for inetd wait mode */ ++extern uschar *initial_cwd; /* The directory we where in at startup */ + extern BOOL is_inetd; /* True for inetd calls */ + extern uschar *iterate_item; /* Item from iterate list */ + +diff -ru a/lookupapi.h b/lookupapi.h +--- a/src/lookupapi.h 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookupapi.h 2017-04-24 09:33:17.364999757 +0200 +@@ -34,7 +34,7 @@ + int, /* length of key or query */ + uschar **, /* for returning answer */ + uschar **, /* for error message */ +- BOOL *); /* to request cache cleanup */ ++ uint *); /* cache TTL, sconds */ + void (*close)( /* close function */ + void *); /* handle */ + void (*tidy)(void); /* tidy function */ +@@ -46,9 +46,10 @@ + } lookup_info; + + /* This magic number is used by the following lookup_module_info structure +- for checking API compatibility. It's equivalent to the string"LMM2" */ +-#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4d32 ++ for checking API compatibility. It used to be equivalent to the string"LMM3" */ ++#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4933 + /* Version 2 adds: version_report */ ++/* Version 3 change: non/cache becomes TTL in seconds */ + + typedef struct lookup_module_info { + uint magic; +diff -ru a/lookups/cdb.c b/lookups/cdb.c +--- a/src/lookups/cdb.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/cdb.c 2017-04-24 09:33:17.364999757 +0200 +@@ -279,7 +279,7 @@ + int key_len, + uschar **result, + uschar **errmsg, +- BOOL *do_cache) ++ uint *do_cache) + { + struct cdb_state * cdbp = handle; + uint32 item_key_len, +diff -ru a/lookups/dbmdb.c b/lookups/dbmdb.c +--- a/src/lookups/dbmdb.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/dbmdb.c 2017-04-24 09:33:17.364999757 +0200 +@@ -87,7 +87,7 @@ + + static int + dbmdb_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + EXIM_DB *d = (EXIM_DB *)handle; + EXIM_DATUM key, data; +@@ -120,7 +120,7 @@ + + int + static dbmnz_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + return dbmdb_find(handle, filename, keystring, length-1, result, errmsg, + do_cache); +@@ -140,7 +140,7 @@ + + static int + dbmjz_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + uschar *key_item, *key_buffer, *key_p; + const uschar *key_elems = keystring; +diff -ru a/lookups/dnsdb.c b/lookups/dnsdb.c +--- a/src/lookups/dnsdb.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/dnsdb.c 2017-04-24 09:33:17.364999757 +0200 +@@ -131,7 +131,7 @@ + + static int + dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int rc; + int size = 256; +@@ -388,6 +388,9 @@ + { + if (rr->type != searchtype) continue; + ++ if (*do_cache > rr->ttl) ++ *do_cache = rr->ttl; ++ + if (type == T_A || type == T_AAAA || type == T_ADDRESSES) + { + dns_address *da; +diff -ru a/lookups/dsearch.c b/lookups/dsearch.c +--- a/src/lookups/dsearch.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/dsearch.c 2017-04-24 09:33:17.364999757 +0200 +@@ -67,7 +67,7 @@ + + int + static dsearch_find(void *handle, uschar *dirname, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + struct stat statbuf; + int save_errno; +diff -ru a/lookups/ibase.c b/lookups/ibase.c +--- a/src/lookups/ibase.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/ibase.c 2017-04-24 09:33:17.364999757 +0200 +@@ -451,7 +451,7 @@ + + static int + ibase_find(void *handle, uschar * filename, uschar * query, int length, +- uschar ** result, uschar ** errmsg, BOOL *do_cache) ++ uschar ** result, uschar ** errmsg, uint *do_cache) + { + int sep = 0; + uschar *server; +diff -ru a/lookups/ldap.c b/lookups/ldap.c +--- a/src/lookups/ldap.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/ldap.c 2017-04-24 09:33:17.364999757 +0200 +@@ -156,7 +156,7 @@ + uschar *error2 = NULL; /* error message from the server */ + uschar *matched = NULL; /* partially matched DN */ + +-int attr_count = 0; ++int attrs_requested = 0; + int error_yield = DEFER; + int msgid; + int rc, ldap_rc, ldap_parse_rc; +@@ -248,7 +248,7 @@ + /* Count the attributes; we need this later to tell us how to format results */ + + for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++) +- attr_count++; ++ attrs_requested++; + + /* See if we can find a cached connection to this host. The port is not + relevant for ldapi. The host name pointer is set to NULL if no host was given +@@ -713,10 +713,15 @@ + LDAP_RES_SEARCH_ENTRY) + { + LDAPMessage *e; ++ int valuecount; /* We can see an attr spread across several ++ entries. If B is derived from A and we request ++ A and the directory contains both, A and B, ++ then we get two entries, one for A and one for B. ++ Here we just count the values per entry */ + + DEBUG(D_lookup) debug_printf("ldap_result loop\n"); + +- for(e = ldap_first_entry(lcp->ld, result); ++ for(e = ldap_first_entry(lcp->ld, result), valuecount = 0; + e != NULL; + e = ldap_next_entry(lcp->ld, e)) + { +@@ -774,6 +779,11 @@ + attr != NULL; + attr = US ldap_next_attribute(lcp->ld, e, ber)) + { ++ ++ /* In case of attrs_requested == 1 we just count the values, in all other cases ++ (0, >1) we count the values per attribute */ ++ if (attrs_requested != 1) valuecount = 0; ++ + if (attr[0] != 0) + { + /* Get array of values for this attribute. */ +@@ -781,7 +791,8 @@ + if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)) + != NULL) + { +- if (attr_count != 1) ++ ++ if (attrs_requested != 1) + { + if (insert_space) + data = string_cat(data, &size, &ptr, US" ", 1); +@@ -795,6 +806,7 @@ + { + uschar *value = *values; + int len = Ustrlen(value); ++ ++valuecount; + + DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value); + +@@ -804,13 +816,13 @@ + * attributeTypes B and C from A and then query for A.) + * In all other cases we detect the different attribute + * and append only every non first value. */ +- if ((attr_count == 1 && data) || (values != firstval)) ++ if (data && valuecount > 1) + data = string_cat(data, &size, &ptr, US",", 1); + + /* For multiple attributes, the data is in quotes. We must escape + internal quotes, backslashes, newlines, and must double commas. */ + +- if (attr_count != 1) ++ if (attrs_requested != 1) + { + int j; + for (j = 0; j < len; j++) +@@ -851,7 +863,7 @@ + + /* Closing quote at the end of the data for a named attribute. */ + +- if (attr_count != 1) ++ if (attrs_requested != 1) + data = string_cat(data, &size, &ptr, US"\"", 1); + + /* Free the values */ +@@ -1339,7 +1351,7 @@ + + static int + eldap_find(void *handle, uschar *filename, const uschar *ldap_url, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + /* Keep picky compilers happy */ + do_cache = do_cache; +@@ -1348,7 +1360,7 @@ + + static int + eldapm_find(void *handle, uschar *filename, const uschar *ldap_url, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + /* Keep picky compilers happy */ + do_cache = do_cache; +@@ -1357,7 +1369,7 @@ + + static int + eldapdn_find(void *handle, uschar *filename, const uschar *ldap_url, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + /* Keep picky compilers happy */ + do_cache = do_cache; +@@ -1366,7 +1378,7 @@ + + int + eldapauth_find(void *handle, uschar *filename, const uschar *ldap_url, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + /* Keep picky compilers happy */ + do_cache = do_cache; +diff -ru a/lookups/lf_functions.h b/lookups/lf_functions.h +--- a/src/lookups/lf_functions.h 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/lf_functions.h 2017-04-24 09:33:17.364999757 +0200 +@@ -12,7 +12,7 @@ + extern uschar *lf_quote(uschar *, uschar *, int, uschar *, int *, int *); + extern int lf_sqlperform(const uschar *, const uschar *, const uschar *, + const uschar *, uschar **, +- uschar **, BOOL *, int(*)(const uschar *, uschar *, uschar **, +- uschar **, BOOL *, BOOL *)); ++ uschar **, uint *, int(*)(const uschar *, uschar *, uschar **, ++ uschar **, BOOL *, uint *)); + + /* End of lf_functions.h */ +diff -ru a/lookups/lf_sqlperform.c b/lookups/lf_sqlperform.c +--- a/src/lookups/lf_sqlperform.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/lf_sqlperform.c 2017-04-24 09:33:17.364999757 +0200 +@@ -27,7 +27,7 @@ + query the query + result where to pass back the result + errmsg where to pass back an error message +- do_cache to be set FALSE if data is changed ++ do_cache to be set zero if data is changed + func the lookup function to call + + Returns: the return from the lookup function, or DEFER +@@ -36,8 +36,8 @@ + int + lf_sqlperform(const uschar *name, const uschar *optionname, + const uschar *optserverlist, const uschar *query, +- uschar **result, uschar **errmsg, BOOL *do_cache, +- int(*fn)(const uschar *, uschar *, uschar **, uschar **, BOOL *, BOOL *)) ++ uschar **result, uschar **errmsg, uint *do_cache, ++ int(*fn)(const uschar *, uschar *, uschar **, uschar **, BOOL *, uint *)) + { + int sep, rc; + uschar *server; +diff -ru a/lookups/lsearch.c b/lookups/lsearch.c +--- a/src/lookups/lsearch.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/lsearch.c 2017-04-24 09:33:17.364999757 +0200 +@@ -323,7 +323,7 @@ + + static int + lsearch_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + do_cache = do_cache; /* Keep picky compilers happy */ + return internal_lsearch_find(handle, filename, keystring, length, result, +@@ -340,7 +340,7 @@ + + static int + wildlsearch_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + do_cache = do_cache; /* Keep picky compilers happy */ + return internal_lsearch_find(handle, filename, keystring, length, result, +@@ -357,7 +357,7 @@ + + static int + nwildlsearch_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + do_cache = do_cache; /* Keep picky compilers happy */ + return internal_lsearch_find(handle, filename, keystring, length, result, +@@ -375,7 +375,7 @@ + + static int + iplsearch_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + do_cache = do_cache; /* Keep picky compilers happy */ + if ((length == 1 && keystring[0] == '*') || +diff -ru a/lookups/mysql.c b/lookups/mysql.c +--- a/src/lookups/mysql.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/mysql.c 2017-04-24 09:33:17.364999757 +0200 +@@ -74,7 +74,7 @@ + resultptr where to store the result + errmsg where to point an error message + defer_break TRUE if no more servers are to be tried after DEFER +- do_cache set false if data is changed ++ do_cache set zero if data is changed + + The server string is of the form "host/dbname/user/password". The host can be + host:port. This string is in a nextinlist temporary buffer, so can be +@@ -85,7 +85,7 @@ + + static int + perform_mysql_search(const uschar *query, uschar *server, uschar **resultptr, +- uschar **errmsg, BOOL *defer_break, BOOL *do_cache) ++ uschar **errmsg, BOOL *defer_break, uint *do_cache) + { + MYSQL *mysql_handle = NULL; /* Keep compilers happy */ + MYSQL_RES *mysql_result = NULL; +@@ -225,7 +225,7 @@ + was expected (this is all explained clearly in the MySQL manual). In this case, + we return the number of rows affected by the command. In this event, we do NOT + want to cache the result; also the whole cache for the handle must be cleaned +-up. Setting do_cache FALSE requests this. */ ++up. Setting do_cache zero requests this. */ + + if ((mysql_result = mysql_use_result(mysql_handle)) == NULL) + { +@@ -233,7 +233,7 @@ + { + DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n"); + result = string_sprintf("%d", mysql_affected_rows(mysql_handle)); +- *do_cache = FALSE; ++ *do_cache = 0; + goto MYSQL_EXIT; + } + *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n", +@@ -341,7 +341,7 @@ + + static int + mysql_find(void *handle, uschar *filename, const uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + return lf_sqlperform(US"MySQL", US"mysql_servers", mysql_servers, query, + result, errmsg, do_cache, perform_mysql_search); +diff -ru a/lookups/nis.c b/lookups/nis.c +--- a/src/lookups/nis.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/nis.c 2017-04-24 09:33:17.364999757 +0200 +@@ -42,7 +42,7 @@ + + static int + nis_find(void *handle, uschar *filename, uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int rc; + uschar *nis_data; +@@ -68,7 +68,7 @@ + + static int + nis0_find(void *handle, uschar *filename, uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int rc; + uschar *nis_data; +diff -ru a/lookups/nisplus.c b/lookups/nisplus.c +--- a/src/lookups/nisplus.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/nisplus.c 2017-04-24 09:33:17.364999757 +0200 +@@ -43,7 +43,7 @@ + + static int + nisplus_find(void *handle, uschar *filename, uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int i; + int ssize = 0; +diff -ru a/lookups/oracle.c b/lookups/oracle.c +--- a/src/lookups/oracle.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/oracle.c 2017-04-24 09:33:17.364999757 +0200 +@@ -517,7 +517,7 @@ + + static int + oracle_find(void *handle, uschar *filename, uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int sep = 0; + uschar *server; +diff -ru a/lookups/passwd.c b/lookups/passwd.c +--- a/src/lookups/passwd.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/passwd.c 2017-04-24 09:33:17.364999757 +0200 +@@ -34,7 +34,7 @@ + + static int + passwd_find(void *handle, uschar *filename, const uschar *keystring, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + struct passwd *pw; + +diff -ru a/lookups/pgsql.c b/lookups/pgsql.c +--- a/src/lookups/pgsql.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/pgsql.c 2017-04-24 09:33:17.364999757 +0200 +@@ -119,7 +119,7 @@ + + static int + perform_pgsql_search(const uschar *query, uschar *server, uschar **resultptr, +- uschar **errmsg, BOOL *defer_break, BOOL *do_cache) ++ uschar **errmsg, BOOL *defer_break, uint *do_cache) + { + PGconn *pg_conn = NULL; + PGresult *pg_result = NULL; +@@ -290,10 +290,10 @@ + /* The command was successful but did not return any data since it was + * not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the + * high level code to not cache this query, and clean the current cache for +- * this handle by setting *do_cache FALSE. */ ++ * this handle by setting *do_cache zero. */ + result = string_copy(US PQcmdTuples(pg_result)); + offset = Ustrlen(result); +- *do_cache = FALSE; ++ *do_cache = 0; + DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data " + "but was successful. Rows affected: %s\n", result); + +@@ -399,7 +399,7 @@ + + static int + pgsql_find(void *handle, uschar *filename, const uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + return lf_sqlperform(US"PostgreSQL", US"pgsql_servers", pgsql_servers, query, + result, errmsg, do_cache, perform_pgsql_search); +diff -ru a/lookups/README b/lookups/README +--- a/src/lookups/README 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/README 2017-04-24 09:33:17.364999757 +0200 +@@ -122,12 +122,15 @@ + uschar **errmsg where to put an error message on failure; + this is initially set to "", and should be left + as that for a standard "entry not found" error +- BOOL *do_cache the lookup should set this to FALSE when it changes data. +- This is TRUE by default. When set to FALSE the cache tree ++ uint *do_cache the lookup should set this to 0 when it changes data. ++ This is MAXINT by default. When set to 0 the cache tree + of the current search handle will be cleaned and the + current result will NOT be cached. Currently the mysql + and pgsql lookups use this when UPDATE/INSERT queries are + executed. ++ If set to a nonzero number of seconds, the cached value ++ becomes unusable after this time. Currently the dnsdb ++ lookup uses this to support the TTL value. + + Even though the key is zero-terminated, the length is passed because in the + common case it has been computed already and is often needed. +diff -ru a/lookups/redis.c b/lookups/redis.c +--- a/src/lookups/redis.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/redis.c 2017-04-24 09:33:17.364999757 +0200 +@@ -65,7 +65,7 @@ + */ + static int + perform_redis_search(uschar *command, uschar *server, uschar **resultptr, +- uschar **errmsg, BOOL *defer_break, BOOL *do_cache) ++ uschar **errmsg, BOOL *defer_break, uint *do_cache) + { + redisContext *redis_handle = NULL; /* Keep compilers happy */ + redisReply *redis_reply = NULL; +@@ -197,7 +197,7 @@ + case REDIS_REPLY_ERROR: + *errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str); + *defer_break = FALSE; +- *do_cache = FALSE; ++ *do_cache = 0; + goto REDIS_EXIT; + /* NOTREACHED */ + +@@ -205,7 +205,7 @@ + case REDIS_REPLY_NIL: + DEBUG(D_lookup) debug_printf("REDIS: query was not one that returned any data\n"); + result = string_sprintf(""); +- *do_cache = FALSE; ++ *do_cache = 0; + goto REDIS_EXIT; + /* NOTREACHED */ + +@@ -304,7 +304,7 @@ + + static int + redis_find(void *handle __attribute__((unused)), uschar *filename __attribute__((unused)), +- uschar *command, int length, uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar *command, int length, uschar **result, uschar **errmsg, uint *do_cache) + { + return lf_sqlperform(US"Redis", US"redis_servers", redis_servers, command, + result, errmsg, do_cache, perform_redis_search); +diff -ru a/lookups/spf.c b/lookups/spf.c +--- a/src/lookups/spf.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/spf.c 2017-04-24 09:33:17.364999757 +0200 +@@ -31,7 +31,9 @@ + #include + #include + +-static void *spf_open(uschar *filename, uschar **errmsg) { ++static void * ++spf_open(uschar *filename, uschar **errmsg) ++{ + SPF_server_t *spf_server = NULL; + spf_server = SPF_server_new(SPF_DNS_CACHE, 0); + if (spf_server == NULL) { +@@ -41,13 +43,17 @@ + return (void *) spf_server; + } + +-static void spf_close(void *handle) { ++static void ++spf_close(void *handle) ++{ + SPF_server_t *spf_server = handle; + if (spf_server) SPF_server_free(spf_server); + } + +-static int spf_find(void *handle, uschar *filename, uschar *keystring, int key_len, +- uschar **result, uschar **errmsg, BOOL *do_cache) { ++static int ++spf_find(void *handle, uschar *filename, uschar *keystring, int key_len, ++ uschar **result, uschar **errmsg, uint *do_cache) ++{ + SPF_server_t *spf_server = handle; + SPF_request_t *spf_request = NULL; + SPF_response_t *spf_response = NULL; +diff -ru a/lookups/sqlite.c b/lookups/sqlite.c +--- a/src/lookups/sqlite.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/sqlite.c 2017-04-24 09:33:17.364999757 +0200 +@@ -81,7 +81,7 @@ + + static int + sqlite_find(void *handle, uschar *filename, const uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + int ret; + struct strbuf res = { NULL, 0, 0 }; +@@ -93,7 +93,7 @@ + return FAIL; + } + +-if (res.string == NULL) *do_cache = FALSE; ++if (res.string == NULL) *do_cache = 0; + + *result = res.string; + return OK; +diff -ru a/lookups/testdb.c b/lookups/testdb.c +--- a/src/lookups/testdb.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/testdb.c 2017-04-24 09:33:17.364999757 +0200 +@@ -38,7 +38,7 @@ + + static int + testdb_find(void *handle, uschar *filename, const uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + handle = handle; /* Keep picky compilers happy */ + filename = filename; +@@ -57,7 +57,7 @@ + return DEFER; + } + +-if (Ustrcmp(query, "nocache") == 0) *do_cache = FALSE; ++if (Ustrcmp(query, "nocache") == 0) *do_cache = 0; + + *result = string_copy(query); + return OK; +diff -ru a/lookups/whoson.c b/lookups/whoson.c +--- a/src/lookups/whoson.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/lookups/whoson.c 2017-04-24 09:33:17.364999757 +0200 +@@ -36,7 +36,7 @@ + + static int + whoson_find(void *handle, uschar *filename, uschar *query, int length, +- uschar **result, uschar **errmsg, BOOL *do_cache) ++ uschar **result, uschar **errmsg, uint *do_cache) + { + uschar buffer[80]; + handle = handle; /* Keep picky compilers happy */ +diff -ru a/mime.c b/mime.c +--- a/src/mime.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/mime.c 2017-04-24 09:33:17.368999807 +0200 +@@ -550,7 +550,8 @@ + uschar * val = string_cat(NULL, &size, &ptr, US"=?", 2); + uschar c; + +-val = string_cat(val, &size, &ptr, charset, Ustrlen(charset)); ++if (charset) ++ val = string_cat(val, &size, &ptr, charset, Ustrlen(charset)); + val = string_cat(val, &size, &ptr, US"?Q?", 3); + + while ((c = *fname)) +@@ -607,7 +608,7 @@ + if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f)) + { + /* Hit EOF or read error. Ugh. */ +- DEBUG(D_acl) debug_printf("Hit EOF ...\n"); ++ DEBUG(D_acl) debug_printf("MIME: Hit EOF ...\n"); + return rc; + } + +@@ -619,12 +620,12 @@ + if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0) + { + /* END boundary found */ +- DEBUG(D_acl) debug_printf("End boundary found %s\n", ++ DEBUG(D_acl) debug_printf("MIME: End boundary found %s\n", + context->boundary); + return rc; + } + +- DEBUG(D_acl) debug_printf("Next part with boundary %s\n", ++ DEBUG(D_acl) debug_printf("MIME: Next part with boundary %s\n", + context->boundary); + break; + } +@@ -648,7 +649,7 @@ + + for (q = p; *q != ';' && *q; q++) ; + *mh->value = string_copynlc(p, q-p); +- DEBUG(D_acl) debug_printf("found %s MIME header, value is '%s'\n", ++ DEBUG(D_acl) debug_printf("MIME: found %s header, value is '%s'\n", + mh->name, *mh->value); + + if (*(p = q)) p++; /* jump past the ; */ +@@ -666,7 +667,7 @@ + { + mime_parameter * mp; + +- DEBUG(D_acl) debug_printf(" considering paramlist '%s'\n", p); ++ DEBUG(D_acl) debug_printf("MIME: considering paramlist '%s'\n", p); + + if ( !mime_filename + && strncmpic(CUS"content-disposition:", header, 20) == 0 +@@ -700,22 +701,27 @@ + uschar * s = q; + + /* look for a ' in the "filename" */ +- while(*s != '\'' && *s) s++; /* s is ' or NUL */ ++ while(*s != '\'' && *s) s++; /* s is 1st ' or NUL */ + + if ((size = s-q) > 0) +- { + mime_filename_charset = string_copyn(q, size); +- p = s; + +- while(*p == '\'' && *p) p++; /* p is after ' */ +- } ++ if (*(p = s)) p++; ++ while(*p == '\'') p++; /* p is after 2nd ' */ + } + else + p = q; + ++ DEBUG(D_acl) debug_printf("MIME: charset %s fname '%s'\n", ++ mime_filename_charset ? mime_filename_charset : US"", p); ++ + temp_string = rfc2231_to_2047(p, mime_filename_charset, &slen); +- temp_string = rfc2047_decode(temp_string, FALSE, NULL, 32, ++ DEBUG(D_acl) debug_printf("MIME: 2047-name %s\n", temp_string); ++ ++ temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ', + NULL, &err_msg); ++ DEBUG(D_acl) debug_printf("MIME: plain-name %s\n", temp_string); ++ + size = Ustrlen(temp_string); + + if (size == slen) +@@ -750,7 +756,7 @@ + &dummy_errstr) + : NULL; + DEBUG(D_acl) debug_printf( +- " found %s MIME parameter in %s header, value '%s'\n", ++ "MIME: found %s parameter in %s header, value '%s'\n", + mp->name, mh->name, *mp->value); + + break; /* done matching param names */ +@@ -768,7 +774,7 @@ + if (decoding_failed) mime_filename = mime_fname_rfc2231; + + DEBUG(D_acl) debug_printf( +- " found %s MIME parameter in %s header, value is '%s'\n", ++ "MIME: found %s parameter in %s header, value is '%s'\n", + "filename", mh->name, mime_filename); + } + } +@@ -809,8 +815,9 @@ + (nested_context.boundary != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) + { +- DEBUG(D_acl) debug_printf("Entering multipart recursion, boundary '%s'\n", +- nested_context.boundary); ++ DEBUG(D_acl) ++ debug_printf("MIME: Entering multipart recursion, boundary '%s'\n", ++ nested_context.boundary); + + nested_context.context = + context && context->context == MBC_ATTACHMENT +diff -ru a/pdkim/base64.c b/pdkim/base64.c +--- a/src/pdkim/base64.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/pdkim/base64.c 2017-04-24 09:33:17.368999807 +0200 +@@ -128,20 +128,22 @@ + + for( i = j = n = 0; i < slen; i++ ) + { ++ unsigned char c = src[i]; ++ + if( ( slen - i ) >= 2 && +- src[i] == '\r' && src[i + 1] == '\n' ) ++ c == '\r' && src[i + 1] == '\n' ) + continue; + +- if( src[i] == '\n' ) ++ if( c == '\n' || c == ' ' || c == '\t' ) + continue; + +- if( src[i] == '=' && ++j > 2 ) ++ if( c == '=' && ++j > 2 ) + return( POLARSSL_ERR_BASE64_INVALID_CHARACTER ); + +- if( src[i] > 127 || base64_dec_map[src[i]] == 127 ) ++ if( c > 127 || base64_dec_map[src[i]] == 127 ) + return( POLARSSL_ERR_BASE64_INVALID_CHARACTER ); + +- if( base64_dec_map[src[i]] < 64 && j != 0 ) ++ if( base64_dec_map[c] < 64 && j != 0 ) + return( POLARSSL_ERR_BASE64_INVALID_CHARACTER ); + + n++; +@@ -160,11 +162,13 @@ + + for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ ) + { +- if( *src == '\r' || *src == '\n' ) ++ unsigned char c = *src; ++ ++ if( c == '\r' || c == '\n' || c == ' ' || c == '\t' ) + continue; + +- j -= ( base64_dec_map[*src] == 64 ); +- x = (x << 6) | ( base64_dec_map[*src] & 0x3F ); ++ j -= ( base64_dec_map[c] == 64 ); ++ x = (x << 6) | ( base64_dec_map[c] & 0x3F ); + + if( ++n == 4 ) + { +diff -ru a/readconf.c b/readconf.c +--- a/src/readconf.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/readconf.c 2017-04-24 09:33:17.368999807 +0200 +@@ -3456,10 +3456,10 @@ + " are obsolete\n"); + #endif /*SUPPORT_TLS*/ + +-if ((!add_environment || *add_environment == '\0') && !keep_environment) ++if (!keep_environment && environ && *environ) + log_write(0, LOG_MAIN, +- "WARNING: purging the environment.\n" +- " Suggested action: use keep_environment and add_environment.\n"); ++ "Warning: purging the environment.\n" ++ " Suggested action: use keep_environment."); + } + + +diff -ru a/receive.c b/receive.c +--- a/src/receive.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/receive.c 2017-04-24 09:33:17.368999807 +0200 +@@ -835,7 +835,15 @@ + ch_state = 4; + continue; + } +- ch_state = 1; /* The dot itself is removed */ ++ /* The dot was removed at state 3. For a doubled dot, here, reinstate ++ it to cutthrough. The current ch, dot or not, is passed both to cutthrough ++ and to file below. */ ++ if (ch == '.') ++ { ++ uschar c= ch; ++ (void) cutthrough_puts(&c, 1); ++ } ++ ch_state = 1; + break; + + case 4: /* After [CR] LF . CR */ +diff -ru a/search.c b/search.c +--- a/src/search.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/search.c 2017-04-24 09:33:17.372999860 +0200 +@@ -466,6 +466,7 @@ + { + tree_node *t = (tree_node *)handle; + search_cache *c = (search_cache *)(t->data.ptr); ++expiring_data *e; + uschar *data = NULL; + int search_type = t->name[0] - '0'; + int old_pool = store_pool; +@@ -491,18 +492,27 @@ + /* Look up the data for the key, unless it is already in the cache for this + file. No need to check c->item_cache for NULL, tree_search will do so. */ + +-if ((t = tree_search(c->item_cache, keystring)) == NULL) ++if ( (t = tree_search(c->item_cache, keystring)) ++ && (!(e = t->data.ptr)->expiry || e->expiry > time(NULL)) ++ ) ++ { /* Data was in the cache already; set the pointer from the tree node */ ++ data = e->ptr; ++ DEBUG(D_lookup) debug_printf("cached data used for lookup of %s%s%s\n", ++ keystring, ++ filename ? US"\n in " : US"", filename ? filename : US""); ++ } ++else + { +- BOOL do_cache = TRUE; ++ uint do_cache = UINT_MAX; + int keylength = Ustrlen(keystring); + + DEBUG(D_lookup) + { +- if (filename != NULL) +- debug_printf("file lookup required for %s\n in %s\n", +- keystring, filename); +- else +- debug_printf("database lookup required for %s\n", keystring); ++ if (t) debug_printf("cached data found but past valid time; "); ++ debug_printf("%s lookup required for %s%s%s\n", ++ filename ? US"file" : US"database", ++ keystring, ++ filename ? US"\n in " : US"", filename ? filename : US""); + } + + /* Call the code for the different kinds of search. DEFER is handled +@@ -511,9 +521,7 @@ + + if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength, + &data, &search_error_message, &do_cache) == DEFER) +- { + search_find_defer = TRUE; +- } + + /* A record that has been found is now in data, which is either NULL + or points to a bit of dynamic store. Cache the result of the lookup if +@@ -524,10 +532,22 @@ + else if (do_cache) + { + int len = keylength + 1; +- t = store_get(sizeof(tree_node) + len); +- memcpy(t->name, keystring, len); +- t->data.ptr = data; +- tree_insertnode(&c->item_cache, t); ++ ++ if (t) /* Previous, out-of-date cache entry. Update with the */ ++ { /* new result and forget the old one */ ++ e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache; ++ e->ptr = data; ++ } ++ else ++ { ++ e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len); ++ e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache; ++ e->ptr = data; ++ t = (tree_node *)(e+1); ++ memcpy(t->name, keystring, len); ++ t->data.ptr = e; ++ tree_insertnode(&c->item_cache, t); ++ } + } + + /* If caching was disabled, empty the cache tree. We just set the cache +@@ -540,34 +560,19 @@ + } + } + +-/* Data was in the cache already; set the pointer from the tree node */ +- +-else +- { +- data = US t->data.ptr; +- DEBUG(D_lookup) debug_printf("cached data used for lookup of %s%s%s\n", +- keystring, +- (filename == NULL)? US"" : US"\n in ", +- (filename == NULL)? US"" : filename); +- } +- +-/* Debug: output the answer */ +- + DEBUG(D_lookup) + { +- if (data == NULL) +- { +- if (search_find_defer) debug_printf("lookup deferred: %s\n", +- search_error_message); +- else debug_printf("lookup failed\n"); +- } +- else debug_printf("lookup yielded: %s\n", data); ++ if (data) ++ debug_printf("lookup yielded: %s\n", data); ++ else if (search_find_defer) ++ debug_printf("lookup deferred: %s\n", search_error_message); ++ else debug_printf("lookup failed\n"); + } + + /* Return it in new dynamic store in the regular pool */ + + store_pool = old_pool; +-return (data == NULL)? NULL : string_copy(data); ++return data ? string_copy(data) : NULL; + } + + +diff -ru a/smtp_in.c b/smtp_in.c +--- a/src/smtp_in.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/smtp_in.c 2017-04-24 09:33:17.372999860 +0200 +@@ -9,6 +9,7 @@ + + + #include "exim.h" ++#include + + + /* Initialize for TCP wrappers if so configured. It appears that the macro +@@ -232,6 +233,7 @@ + + /* Sanity check and validate optional args to MAIL FROM: envelope */ + enum { ++ ENV_MAIL_OPT_NULL, + ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, + #ifndef DISABLE_PRDR + ENV_MAIL_OPT_PRDR, +@@ -240,7 +242,6 @@ + #ifdef EXPERIMENTAL_INTERNATIONAL + ENV_MAIL_OPT_UTF8, + #endif +- ENV_MAIL_OPT_NULL + }; + typedef struct { + uschar * name; /* option requested during MAIL cmd */ +@@ -260,7 +261,8 @@ + #ifdef EXPERIMENTAL_INTERNATIONAL + { US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */ + #endif +- { US"NULL", ENV_MAIL_OPT_NULL, FALSE } ++ /* keep this the last entry */ ++ { US"NULL", ENV_MAIL_OPT_NULL, FALSE }, + }; + + /* When reading SMTP from a remote host, we have to use our own versions of the +@@ -3887,7 +3889,7 @@ + if (!extract_option(&name, &value)) break; + + for (mail_args = env_mail_type_list; +- (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list); ++ mail_args->value != ENV_MAIL_OPT_NULL; + mail_args++ + ) + if (strcmpic(name, mail_args->name) == 0) +@@ -4066,15 +4068,17 @@ + } + break; + #endif +- /* Unknown option. Stick back the terminator characters and break ++ /* No valid option. Stick back the terminator characters and break + the loop. Do the name-terminator second as extract_option sets +- value==name when it found no equal-sign. +- An error for a malformed address will occur. */ +- default: ++ value==name when it found no equal-sign. ++ An error for a malformed address will occur. */ ++ case ENV_MAIL_OPT_NULL: + value[-1] = '='; + name[-1] = ' '; + arg_error = TRUE; + break; ++ ++ default: assert(0); + } + /* Break out of for loop if switch() had bad argument or + when start of the email address is reached */ +diff -ru a/smtp_out.c b/smtp_out.c +--- a/src/smtp_out.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/smtp_out.c 2017-04-24 09:33:17.372999860 +0200 +@@ -26,7 +26,6 @@ + which case the function does nothing + host_af AF_INET or AF_INET6 for the outgoing IP address + addr the mail address being handled (for setting errors) +- changed if not NULL, set TRUE if expansion actually changed istring + interface point this to the interface + msg to add to any error message + +@@ -36,7 +35,7 @@ + + BOOL + smtp_get_interface(uschar *istring, int host_af, address_item *addr, +- BOOL *changed, uschar **interface, uschar *msg) ++ uschar **interface, uschar *msg) + { + const uschar * expint; + uschar *iface; +@@ -54,8 +53,6 @@ + return FALSE; + } + +-if (changed != NULL) *changed = expint != istring; +- + while (isspace(*expint)) expint++; + if (*expint == 0) return TRUE; + +diff -ru a/structs.h b/structs.h +--- a/src/structs.h 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/structs.h 2017-04-24 09:33:17.372999860 +0200 +@@ -657,6 +657,16 @@ + uschar name[1]; /* node name - variable length */ + } tree_node; + ++/* Structure for holding time-limited data such as DNS returns. ++We use this rather than extending tree_node to avoid wasting ++space for most tree use (variables...) at the cost of complexity ++for the lookups cache */ ++ ++typedef struct expiring_data { ++ time_t expiry; /* if nonzero, data invalid after this time */ ++ void *ptr; /* pointer to data */ ++} expiring_data; ++ + /* Structure for holding the handle and the cached last lookup for searches. + This block is pointed to by the tree entry for the file. The file can get + closed if too many are opened at once. There is a LRU chain for deciding which +@@ -676,6 +686,7 @@ + typedef struct { + uschar name[DNS_MAXNAME]; /* domain name */ + int type; /* record type */ ++ unsigned short ttl; /* time-to-live, seconds */ + int size; /* size of data */ + uschar *data; /* pointer to data */ + } dns_record; +diff -ru a/tls-gnu.c b/tls-gnu.c +--- a/src/tls-gnu.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/tls-gnu.c 2017-04-24 09:33:17.376999910 +0200 +@@ -1729,6 +1729,7 @@ + + if (!sigalrm_seen) + { ++ gnutls_certificate_free_credentials(state->x509_cred); + (void)fclose(smtp_out); + (void)fclose(smtp_in); + } +@@ -2014,6 +2015,8 @@ + } + + gnutls_deinit(state->session); ++gnutls_certificate_free_credentials(state->x509_cred); ++ + + state->tlsp->active = -1; + memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); +@@ -2074,6 +2077,8 @@ + receive_smtp_buffered = smtp_buffered; + + gnutls_deinit(state->session); ++ gnutls_certificate_free_credentials(state->x509_cred); ++ + state->session = NULL; + state->tlsp->active = -1; + state->tlsp->bits = 0; +diff -ru a/transport.c b/transport.c +--- a/src/transport.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/transport.c 2017-04-24 09:33:17.376999910 +0200 +@@ -1752,7 +1752,7 @@ + { + if (split_spool_directory) + sprintf(CS spool_file, "%s%c/%s-D", +- spool_dir, new_message_id[5], msgq[i].message_id); ++ spool_dir, msgq[i].message_id[5], msgq[i].message_id); + else + sprintf(CS spool_file, "%s%s-D", spool_dir, msgq[i].message_id); + +diff -ru a/transports/smtp.c b/transports/smtp.c +--- a/src/transports/smtp.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/transports/smtp.c 2017-04-24 09:33:17.376999910 +0200 +@@ -1274,14 +1274,19 @@ + static BOOL + smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare) + { +-uschar * save_sender_address = sender_address; +-uschar * current_local_identity = ++ ++uschar * message_local_identity, ++ * current_local_identity, ++ * new_sender_address; ++ ++current_local_identity = + smtp_local_identity(s_compare->current_sender_address, s_compare->tblock); +-uschar * new_sender_address = deliver_get_sender_address(message_id); +-uschar * message_local_identity = +- smtp_local_identity(new_sender_address, s_compare->tblock); + +-sender_address = save_sender_address; ++if (!(new_sender_address = deliver_get_sender_address(message_id))) ++ return 0; ++ ++message_local_identity = ++ smtp_local_identity(new_sender_address, s_compare->tblock); + + return Ustrcmp(current_local_identity, message_local_identity) == 0; + } +@@ -3169,7 +3174,6 @@ + BOOL serialized = FALSE; + BOOL host_is_expired = FALSE; + BOOL message_defer = FALSE; +- BOOL ifchanges = FALSE; + BOOL some_deferred = FALSE; + address_item *first_addr = NULL; + uschar *interface = NULL; +@@ -3345,15 +3349,18 @@ + if (Ustrcmp(pistring, ":25") == 0) pistring = US""; + + /* Select IPv4 or IPv6, and choose an outgoing interface. If the interface +- string changes upon expansion, we must add it to the key that is used for +- retries, because connections to the same host from a different interface +- should be treated separately. */ ++ string is set, even if constant (as different transports can have different ++ constant settings), we must add it to the key that is used for retries, ++ because connections to the same host from a different interface should be ++ treated separately. */ + + host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET : AF_INET6; +- if (!smtp_get_interface(ob->interface, host_af, addrlist, &ifchanges, +- &interface, tid)) +- return FALSE; +- if (ifchanges) pistring = string_sprintf("%s/%s", pistring, interface); ++ if ((rs = ob->interface) && *rs) ++ { ++ if (!smtp_get_interface(rs, host_af, addrlist, &interface, tid)) ++ return FALSE; ++ pistring = string_sprintf("%s/%s", pistring, interface); ++ } + + /* The first time round the outer loop, check the status of the host by + inspecting the retry data. The second time round, we are interested only +diff -ru a/verify.c b/verify.c +--- a/src/verify.c 2016-03-02 18:27:51.000000000 +0100 ++++ b/src/verify.c 2017-04-24 09:33:17.380999961 +0200 +@@ -21,6 +21,7 @@ + /* Structure for caching DNSBL lookups */ + + typedef struct dnsbl_cache_block { ++ time_t expiry; + dns_address *rhs; + uschar *text; + int rc; +@@ -443,7 +444,7 @@ + + host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; + +- if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface, ++ if (!smtp_get_interface(tf->interface, host_af, addr, &interface, + US"callout") || + !smtp_get_port(tf->port, addr, &port, US"callout")) + log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, +@@ -578,7 +579,7 @@ + deliver_domain = addr->domain; + transport_name = addr->transport->name; + +- if ( !smtp_get_interface(tf->interface, host_af, addr, NULL, &interface, ++ if ( !smtp_get_interface(tf->interface, host_af, addr, &interface, + US"callout") + || !smtp_get_port(tf->port, addr, &port, US"callout") + ) +@@ -3584,21 +3585,37 @@ + + /* Look for this query in the cache. */ + +-t = tree_search(dnsbl_cache, query); ++if ( (t = tree_search(dnsbl_cache, query)) ++ && (cb = t->data.ptr)->expiry > time(NULL) ++ ) ++ ++/* Previous lookup was cached */ ++ ++ { ++ HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n"); ++ } + + /* If not cached from a previous lookup, we must do a DNS lookup, and + cache the result in permanent memory. */ + +-if (t == NULL) ++else + { ++ uint ttl = 3600; ++ + store_pool = POOL_PERM; + +- /* Set up a tree entry to cache the lookup */ ++ if (t) ++ { ++ HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; "); ++ } + +- t = store_get(sizeof(tree_node) + Ustrlen(query)); +- Ustrcpy(t->name, query); +- t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block)); +- (void)tree_insertnode(&dnsbl_cache, t); ++ else ++ { /* Set up a tree entry to cache the lookup */ ++ t = store_get(sizeof(tree_node) + Ustrlen(query)); ++ Ustrcpy(t->name, query); ++ t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block)); ++ (void)tree_insertnode(&dnsbl_cache, t); ++ } + + /* Do the DNS loopup . */ + +@@ -3616,7 +3633,10 @@ + + Quite apart from one A6 RR generating multiple addresses, there are DNS + lists that return more than one A record, so we must handle multiple +- addresses generated in that way as well. */ ++ addresses generated in that way as well. ++ ++ Mark the cache entry with the "now" plus the minimum of the address TTLs, ++ or some suitably far-future time if none were found. */ + + if (cb->rc == DNS_SUCCEED) + { +@@ -3634,6 +3654,7 @@ + *addrp = da; + while (da->next != NULL) da = da->next; + addrp = &(da->next); ++ if (ttl > rr->ttl) ttl = rr->ttl; + } + } + } +@@ -3645,17 +3666,10 @@ + if (cb->rhs == NULL) cb->rc = DNS_NODATA; + } + ++ cb->expiry = time(NULL)+ttl; + store_pool = old_pool; + } + +-/* Previous lookup was cached */ +- +-else +- { +- HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n"); +- cb = t->data.ptr; +- } +- + /* We now have the result of the DNS lookup, either newly done, or cached + from a previous call. If the lookup succeeded, check against the address + list if there is one. This may be a positive equality list (introduced by +Only in a: version.sh diff --git a/exim.changes b/exim.changes index 586add7..f8012bf 100644 --- a/exim.changes +++ b/exim.changes @@ -1,3 +1,18 @@ +------------------------------------------------------------------- +Mon Apr 24 07:45:00 UTC 2017 - wullinger@rz.uni-kiel.de + +- conditionally disable DANE on SuSE versions with OpenSSL < 1.0 + +- import exim-4_86_2+fixes branch + + fix CVE-2016-1531 + when installed setuid root, allows local users to gain privileges via the perl_startup + argument. + + fix Bug 1805: store the initial working directory, expand $initial_cwd + + fix Bug 1671: segfault after delivery (https://bugs.exim.org/show_bug.cgi?id=1671) + + Don't issue env warning if env is empty + +- fix CVE-2016-9963: DKIM information leakage + ------------------------------------------------------------------- Mon Apr 4 15:55:31 UTC 2016 - e.istomin@edss.ee diff --git a/exim.spec b/exim.spec index 0f23066..070d756 100644 --- a/exim.spec +++ b/exim.spec @@ -1,7 +1,7 @@ # # spec file for package exim # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,6 +20,14 @@ %bcond_without pgsql %bcond_without sqlite %bcond_without ldap +%if 0%{?suse_version} < 1199 || 0%{?centos_version} < 599 || 0%{?rhel_version} < 599 +%bcond_with dane +%else +%bcond_without dane +%endif +# disable for now, +# since utf8_downconvert currently crashes +%bcond_without i18n Name: exim BuildRequires: cyrus-sasl-devel @@ -72,8 +80,8 @@ Summary: The Exim Mail Transfer Agent, a Replacement for sendmail License: GPL-2.0+ Group: Productivity/Networking/Email/Servers BuildRoot: %{_tmppath}/%{name}-%{version}-build -Source: http://ftp.exim.org/pub/exim/exim4/exim-%{version}.tar.bz2 -Source3: http://ftp.exim.org/pub/exim/exim4/exim-%{version}.tar.bz2.asc +Source: http://ftp.exim.org/pub/exim/exim4/old/exim-%{version}.tar.bz2 +Source3: http://ftp.exim.org/pub/exim/exim4/old/exim-%{version}.tar.bz2.asc # http://ftp.exim.org/pub/exim/Exim-Maintainers-Keyring.asc Source4: exim.keyring Source1: sysconfig.exim @@ -86,7 +94,9 @@ Source30: eximstats-html-update.py Source31: eximstats.conf Source32: eximstats.conf-2.2 Source40: exim.service -Patch: exim-tail.patch +Patch0: exim-tail.patch +Patch1: exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch +Patch2: fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch %package -n eximon Summary: Eximon, an graphical frontend to administer Exim's mail queue @@ -128,7 +138,9 @@ once, if at all. The rest is done by logrotate / cron.) %prep %setup -q -n exim-%{version} -%patch +%patch0 +%patch1 -p 1 +%patch2 -p 1 # build with fPIE/pie on SUSE 10.0 or newer, or on any other platform %if %{?suse_version:%suse_version}%{?!suse_version:99999} > 930 fPIE="-fPIE" @@ -264,9 +276,13 @@ cat <<-EOF > Local/Makefile EXPERIMENTAL_PROXY=yes EXPERIMENTAL_CERTNAMES=yes EXPERIMENTAL_DSN=yes +%if %{with dane} EXPERIMENTAL_DANE=yes +%endif EXPERIMENTAL_SOCKS=yes +%if %{with i18n} EXPERIMENTAL_INTERNATIONAL=yes +%endif LDFLAGS += -lidn CFLAGS=$RPM_OPT_FLAGS -Wall $CFLAGS_OPT_WERROR -fno-strict-aliasing -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DLDAP_DEPRECATED $fPIE EXTRALIBS=-ldl -lpam -L/usr/X11R6/%{_lib} $pie @@ -465,7 +481,7 @@ exit 0 %attr(0750,root,www) /srv/www/eximstats %dir /etc/apache2 %dir /etc/apache2/conf.d -/etc/apache2/conf.d/eximstats.conf +%config /etc/apache2/conf.d/eximstats.conf %{_sbindir}/eximstats-html-update.py %changelog diff --git a/fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch b/fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch new file mode 100644 index 0000000..11d5abc --- /dev/null +++ b/fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch @@ -0,0 +1,66 @@ +From 31c02defdc5118834e801d4fe8f11c1d9b5ebadf Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 16 Dec 2016 20:36:39 +0000 +Subject: [PATCH 1/3] Fix DKIM information leakage + +Cherry picked from exim-4_87 .. exim-4_87_1 +--- + doc/doc-txt/ChangeLog | 7 +++ + doc/doc-txt/cve-2016-9663 | 86 +++++++++++++++++++++++++++++++++ + src/src/dkim.c | 1 + + src/src/transports/smtp.c | 4 +- + test/confs/4510 | 71 +++++++++++++++++++++++++++ + test/log/4510 | 20 ++++++++ + test/mail/4510.store | 58 ++++++++++++++++++++++ + test/runtest | 8 +++ + test/scripts/4510-DKIM-Bounces/4510 | 15 ++++++ + test/scripts/4510-DKIM-Bounces/REQUIRES | 2 + + 10 files changed, 271 insertions(+), 1 deletion(-) + create mode 100644 doc/doc-txt/cve-2016-9663 + create mode 100644 test/confs/4510 + create mode 100644 test/log/4510 + create mode 100644 test/mail/4510.store + create mode 100644 test/scripts/4510-DKIM-Bounces/4510 + create mode 100644 test/scripts/4510-DKIM-Bounces/REQUIRES + +diff --git a/src/dkim.c b/src/dkim.c +index 3e71545..8e93566 100644 +--- a/src/dkim.c ++++ b/src/dkim.c +@@ -519,6 +519,7 @@ dkim_exim_sign(int dkim_fd, uschar *dkim_private_key, + (char *)dkim_signing_selector, + (char *)dkim_private_key_expanded + ); ++ dkim_private_key_expanded[0] = '\0'; + + pdkim_set_debug_stream(ctx,debug_file); + +diff --git a/src/transports/smtp.c b/src/transports/smtp.c +index a952413..cc8f025 100644 +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -293,6 +293,7 @@ static uschar *rf_names[] = { US"NEVER", US"SUCCESS", US"FAILURE", US"DELAY" }; + static uschar *smtp_command; /* Points to last cmd for error messages */ + static uschar *mail_command; /* Points to MAIL cmd for error messages */ + static BOOL update_waiting; /* TRUE to update the "wait" database */ ++static uschar *data_command = US""; /* Points to DATA cmd for error messages */ + + + /************************************************* +@@ -2244,6 +2245,7 @@ if (ok || (smtp_use_pipelining && !mua_wrapper)) + case -1: goto END_OFF; /* Timeout on RCPT */ + default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + } ++ data_command = string_copy(big_buffer); /* Save for later error message */ + } + + /* Save the first address of the next batch. */ +@@ -2418,7 +2420,7 @@ if (!ok) ok = TRUE; else + #else + "LMTP error after %s: %s", + #endif +- big_buffer, string_printing(buffer)); ++ data_command, string_printing(buffer)); + setflag(addr, af_pass_message); /* Allow message to go to user */ + if (buffer[0] == '5') + addr->transport_return = FAIL; From 048e70792768c1d0be58df0ffd46467e011445e46798b2b13e84288a835a1f68 Mon Sep 17 00:00:00 2001 From: Marcus Rueckert Date: Thu, 22 Jun 2017 14:02:16 +0000 Subject: [PATCH 2/3] Accepting request 504846 from home:msmeissn:branches:server:mail - exim-CVE-2017-1000369.patch: Fixed memory leaks that could be exploited to "stack crash" local privilege escalation (bsc#1044692) - Require user(mail) group(mail) to meet new users handling in TW. - Prerequire permissions (fixes rpmlint). CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch OBS-URL: https://build.opensuse.org/request/show/504846 OBS-URL: https://build.opensuse.org/package/show/server:mail/exim?expand=0&rev=184 --- exim-CVE-2017-1000369.patch | 43 +++++++++++++++++++++++++++++++++++++ exim.changes | 11 ++++++++++ exim.spec | 11 ++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 exim-CVE-2017-1000369.patch diff --git a/exim-CVE-2017-1000369.patch b/exim-CVE-2017-1000369.patch new file mode 100644 index 0000000..13d70fa --- /dev/null +++ b/exim-CVE-2017-1000369.patch @@ -0,0 +1,43 @@ +commit 65e061b76867a9ea7aeeb535341b790b90ae6c21 +Author: Heiko Schlittermann (HS12-RIPE) +Date: Wed May 31 23:08:56 2017 +0200 + + Cleanup (prevent repeated use of -p/-oMr to avoid mem leak) + +diff --git a/src/exim.c b/src/src/exim.c +index 67583e58..88e11977 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -3106,7 +3106,14 @@ for (i = 1; i < argc; i++) + + /* -oMr: Received protocol */ + +- else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i]; ++ else if (Ustrcmp(argrest, "Mr") == 0) ++ ++ if (received_protocol) ++ { ++ fprintf(stderr, "received_protocol is set already\n"); ++ exit(EXIT_FAILURE); ++ } ++ else received_protocol = argv[++i]; + + /* -oMs: Set sender host name */ + +@@ -3202,7 +3209,15 @@ for (i = 1; i < argc; i++) + + if (*argrest != 0) + { +- uschar *hn = Ustrchr(argrest, ':'); ++ uschar *hn; ++ ++ if (received_protocol) ++ { ++ fprintf(stderr, "received_protocol is set already\n"); ++ exit(EXIT_FAILURE); ++ } ++ ++ hn = Ustrchr(argrest, ':'); + if (hn == NULL) + { + received_protocol = argrest; diff --git a/exim.changes b/exim.changes index f8012bf..29faac6 100644 --- a/exim.changes +++ b/exim.changes @@ -1,3 +1,13 @@ +------------------------------------------------------------------- +Mon Jun 19 16:27:45 UTC 2017 - meissner@suse.com + +- exim-CVE-2017-1000369.patch: Fixed memory leaks that could be + exploited to "stack crash" local privilege escalation (bsc#1044692) + +- Require user(mail) group(mail) to meet new users handling in TW. + +- Prerequire permissions (fixes rpmlint). + ------------------------------------------------------------------- Mon Apr 24 07:45:00 UTC 2017 - wullinger@rz.uni-kiel.de @@ -12,6 +22,7 @@ Mon Apr 24 07:45:00 UTC 2017 - wullinger@rz.uni-kiel.de + Don't issue env warning if env is empty - fix CVE-2016-9963: DKIM information leakage + CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch ------------------------------------------------------------------- Mon Apr 4 15:55:31 UTC 2016 - e.istomin@edss.ee diff --git a/exim.spec b/exim.spec index 070d756..8e9459f 100644 --- a/exim.spec +++ b/exim.spec @@ -61,8 +61,13 @@ BuildRequires: pkgconfig(systemd) %else Requires(pre): %insserv_prereq %endif -Requires(pre): %fillup_prereq -Requires(pre): /usr/sbin/useradd +Requires(pre): %fillup_prereq permissions +%if 0%{?suse_version} >= 1330 +BuildRequires: group(mail) +BuildRequires: user(mail) +Requires(pre): user(mail) +Requires(pre): group(mail) +%endif Requires(pre): fileutils textutils %endif Version: 4.86.2 @@ -97,6 +102,7 @@ Source40: exim.service Patch0: exim-tail.patch Patch1: exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch Patch2: fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch +Patch3: exim-CVE-2017-1000369.patch %package -n eximon Summary: Eximon, an graphical frontend to administer Exim's mail queue @@ -141,6 +147,7 @@ once, if at all. The rest is done by logrotate / cron.) %patch0 %patch1 -p 1 %patch2 -p 1 +%patch3 -p 1 # build with fPIE/pie on SUSE 10.0 or newer, or on any other platform %if %{?suse_version:%suse_version}%{?!suse_version:99999} > 930 fPIE="-fPIE" From 45d7c59c3b10f4978ace208ce1c03e84eea38bfb444d1908b0a5f7b3cb942088 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Fri, 30 Jun 2017 13:40:09 +0000 Subject: [PATCH 3/3] Accepting request 506440 from home:pwcau:branches:server:mail cleanup changelog and properly reference the patch files to (hopefully) make the patch-reference checker in :Factory happy. OBS-URL: https://build.opensuse.org/request/show/506440 OBS-URL: https://build.opensuse.org/package/show/server:mail/exim?expand=0&rev=185 --- exim.changes | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/exim.changes b/exim.changes index 29faac6..a591a3b 100644 --- a/exim.changes +++ b/exim.changes @@ -13,7 +13,8 @@ Mon Apr 24 07:45:00 UTC 2017 - wullinger@rz.uni-kiel.de - conditionally disable DANE on SuSE versions with OpenSSL < 1.0 -- import exim-4_86_2+fixes branch +- exim-4.86.2+fixes-867e8fe25dbfb1e31493488ad695bde55b890397.patch: + import exim-4_86_2+fixes branch + fix CVE-2016-1531 when installed setuid root, allows local users to gain privileges via the perl_startup argument. @@ -21,8 +22,9 @@ Mon Apr 24 07:45:00 UTC 2017 - wullinger@rz.uni-kiel.de + fix Bug 1671: segfault after delivery (https://bugs.exim.org/show_bug.cgi?id=1671) + Don't issue env warning if env is empty -- fix CVE-2016-9963: DKIM information leakage - CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch +- fix-CVE-2016-9963-31c02defdc5118834e801d4fe8f11c1d9b5ebadf.patch: + DKIM information leakage + ------------------------------------------------------------------- Mon Apr 4 15:55:31 UTC 2016 - e.istomin@edss.ee