From d6f6e0dc45e55bf5edd4c16e2b360ab2031d5468 Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Tue, 3 Oct 2006 15:11:22 +0000 Subject: [PATCH] Michael Deutschmann's patch for getting TXT from a specific list when a match is found on a merged list. --- doc/doc-txt/ChangeLog | 6 +- doc/doc-txt/NewStuff | 46 +++++++++++- src/ACKNOWLEDGMENTS | 5 +- src/src/verify.c | 136 +++++++++++++++++++++++------------ test/confs/0139 | 4 ++ test/dnszones-src/db.test.ex | 7 +- test/log/0139 | 3 + test/mail/0139.userx | 14 ++++ test/scripts/0000-Basic/0139 | 8 +++ test/stderr/0139 | 34 ++++++++- test/stdout/0139 | 8 ++- 11 files changed, 219 insertions(+), 52 deletions(-) create mode 100644 test/log/0139 create mode 100644 test/mail/0139.userx diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 111d2ae5c..e7d4a3f55 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.402 2006/10/03 10:25:55 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.403 2006/10/03 15:11:22 ph10 Exp $ Change log file for Exim from version 4.21 ------------------------------------------- @@ -91,6 +91,10 @@ PH/14 Corrected misleading output from -bv when -v was also used. Suppose the with its parentage. It now outputs "B failed to verify", showing B's parentage before showing the successful verification of C. +PH/15 Applied Michael Deutschmann's patch to allow DNS black list processing to + look up a TXT record in a specific list after matching in a combined + list. + Exim version 4.63 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 8a509414b..4ee55fdcf 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/NewStuff,v 1.114 2006/10/03 08:54:50 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/NewStuff,v 1.115 2006/10/03 15:11:22 ph10 Exp $ New Features in Exim -------------------- @@ -74,7 +74,49 @@ Version 4.64 4. The variable $message_headers_raw provides a concatenation of all the messages's headers without any decoding. This is in contrast to - $message_headers, which does RFC2047 encoding on the header contents. + $message_headers, which does RFC2047 decoding on the header contents. + +5. In a DNS black list, when the facility for restricting the matching IP + values is used, the text from the TXT record that is set in $dnslist_text + may not reflect the true reason for rejection. This happens when lists are + merged and the IP address in the A record is used to distinguish them; + unfortunately there is only one TXT record. One way round this is not to use + merged lists, but that can be inefficient because it requires multiple DNS + lookups where one would do in the vast majority of cases when the host of + interest is not on any of the lists. + + A less inefficient way of solving this problem has now been implemented. If + two domain names, comma-separated, are given, the second is used first to do + an initial check, making use of any IP value restrictions that are set. If + there is a match, the first domain is used, without any IP value + restrictions, to get the TXT record. As a byproduct of this, there is also a + check that the IP being tested is indeed on the first list. The first domain + is the one that is put in $dnslist_domain. For example: + + reject message = rejected because $sender_ip_address is blacklisted \ + at $dnslist_domain\n$dnslist_text + dnslists = sbl.spamhaus.org,sbl-xbl.spamhaus.org=127.0.0.2 : \ + dul.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.10 + + For the first blacklist item, this starts by doing a lookup in + sbl-xbl.spamhaus.org and testing for a 127.0.0.2 return. If there is a + match, it then looks in sbl.spamhaus.org, without checking the return value, + and as long as something is found, it looks for the corresponding TXT + record. If there is no match in sbl-xbl.spamhaus.org, nothing more is done. + The second blacklist item is processed similarly. + + If you are interested in more than one merged list, the same list must be + given several times, but because the results of the DNS lookups are cached, + the DNS calls themselves are not repeated. For example: + + reject dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \ + socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 : \ + misc.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.4 : \ + dul.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.10 + + In this case there is a lookup in dnsbl.sorbs.net, and if none of the IP + values matches (or if no record is found), this is the only lookup that is + done. Only if there is a match is one of the more specific lists consulted. Version 4.63 diff --git a/src/ACKNOWLEDGMENTS b/src/ACKNOWLEDGMENTS index 0a9259481..d5c19bce6 100644 --- a/src/ACKNOWLEDGMENTS +++ b/src/ACKNOWLEDGMENTS @@ -1,4 +1,4 @@ -$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.56 2006/10/02 13:38:18 ph10 Exp $ +$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.57 2006/10/03 15:11:22 ph10 Exp $ EXIM ACKNOWLEDGEMENTS @@ -20,7 +20,7 @@ relatively small patches. Philip Hazel Lists created: 20 November 2002 -Last updated: 02 October 2006 +Last updated: 03 October 2006 THE OLD LIST @@ -100,6 +100,7 @@ Michael Deutschmann Suggested patch for treating bind() failure like conne Patch for $sender_data and $recipient_data Suggested patch for null address match lookup bug Suggested patch for verify = not_blind + Patch for alternate TXT lookup in DNS lists Oliver Eikemeier Patch to skip Received: if expansion is empty Patch for "eqi" Nico Erfurth Fix for bug in ${readfile} diff --git a/src/src/verify.c b/src/src/verify.c index d180fdaa9..4d0fe69d2 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/verify.c,v 1.40 2006/10/03 10:25:55 ph10 Exp $ */ +/* $Cambridge: exim/src/src/verify.c,v 1.41 2006/10/03 15:11:22 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -2483,6 +2483,12 @@ else } } #endif + +/* Remove trailing period -- this is needed so that both arbitrary +dnsbl keydomains and inverted addresses may be combined with the +same format string, "%s.%s" */ + +*(--bptr) = 0; } @@ -2491,13 +2497,19 @@ else * Perform a single dnsbl lookup * *************************************************/ -/* This function is called from verify_check_dnsbl() below. +/* This function is called from verify_check_dnsbl() below. It is also called +recursively from within itself when domain and domain_txt are different +pointers, in order to get the TXT record from the alternate domain. Arguments: - domain the outer dnsbl domain (for debug message) + domain the outer dnsbl domain + domain_txt alternate domain to lookup TXT record on success; when the + same domain is to be used, domain_txt == domain (that is, + the pointers must be identical, not just the text) keydomain the current keydomain (for debug message) - query the domain to be looked up - iplist the list of matching IP addresses + prepend subdomain to lookup (like keydomain, but + reversed if IP address) + iplist the list of matching IP addresses, or NULL for "any" bitmask true if bitmask matching is wanted invert_result true if result to be inverted defer_return what to return for a defer @@ -2507,14 +2519,25 @@ Returns: OK if lookup succeeded */ static int -one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query, - uschar *iplist, BOOL bitmask, BOOL invert_result, int defer_return) +one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain, + uschar *prepend, uschar *iplist, BOOL bitmask, BOOL invert_result, + int defer_return) { dns_answer dnsa; dns_scan dnss; tree_node *t; dnsbl_cache_block *cb; int old_pool = store_pool; +uschar query[256]; /* DNS domain max length */ + +/* Construct the specific query domainname */ + +if (!string_format(query, sizeof(query), "%s.%s", prepend, domain)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " + "(ignored): %s...", query); + return FAIL; + } /* Look for this query in the cache. */ @@ -2679,8 +2702,18 @@ if (cb->rc == DNS_SUCCEED) } } - /* Either there was no IP list, or the record matched. Look up a TXT record - if it hasn't previously been done. */ + /* Either there was no IP list, or the record matched, implying that the + domain is on the list. We now want to find a corresponding TXT record. If an + alternate domain is specified for the TXT record, call this function + recursively to look that up; this has the side effect of re-checking that + there is indeed an A record at the alternate domain. */ + + if (domain_txt != domain) + return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL, + FALSE, invert_result, defer_return); + + /* If there is no alternate domain, look up a TXT record in the main domain + if it has not previously been cached. */ if (!cb->text_set) { @@ -2751,7 +2784,7 @@ given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. If no key is given, what is looked up in the domain is the inverted IP address of the current client host. If a key is given, it is used to construct the -domain for the lookup. For example, +domain for the lookup. For example: dsn.rfc-ignorant.org/$sender_address_domain @@ -2760,6 +2793,17 @@ then we check for a TXT record for an error message, and if found, save its value in $dnslist_text. We also cache everything in a tree, to optimize multiple lookups. +The TXT record is normally looked up in the same domain as the A record, but +when many lists are combined in a single DNS domain, this will not be a very +specific message. It is possible to specify a different domain for looking up +TXT records; this is given before the main domain, comma-separated. For +example: + + dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \ + socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 + +The caching ensures that only one lookup in dnsbl.sorbs.net is done. + Note: an address for testing RBL is 192.203.178.39 Note: an address for testing DUL is 192.203.178.4 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org @@ -2784,7 +2828,6 @@ uschar *list = *listptr; uschar *domain; uschar *s; uschar buffer[1024]; -uschar query[256]; /* DNS domain max length */ uschar revadd[128]; /* Long enough for IPv6 address */ /* Indicate that the inverted IP address is not yet set up */ @@ -2800,8 +2843,9 @@ dns_init(FALSE, FALSE); while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; - BOOL frc; BOOL bitmask = FALSE; + uschar *domain_txt; + uschar *comma; uschar *iplist; uschar *key; @@ -2846,6 +2890,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL *iplist++ = 0; } + /* If there is a comma in the domain, it indicates that a second domain for + looking up TXT records is provided, before the main domain. Otherwise we must + set domain_txt == domain. */ + + domain_txt = domain; + comma = Ustrchr(domain, ','); + if (comma != NULL) + { + *comma++ = 0; + domain = comma; + } + /* Check that what we have left is a sensible domain name. There is no reason why these domains should in fact use the same syntax as hosts and email domains, but in practice they seem to. However, there is little point in @@ -2862,6 +2918,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL } } + /* Check the alternate domain if present */ + + if (domain_txt != domain) for (s = domain_txt; *s != 0; s++) + { + if (!isalnum(*s) && *s != '-' && *s != '.') + { + log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " + "strange characters - is this right?", domain_txt); + break; + } + } + /* If there is no key string, construct the query by adding the domain name onto the inverted host address, and perform a single DNS lookup. */ @@ -2869,25 +2937,14 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL { if (sender_host_address == NULL) return FAIL; /* can never match */ if (revadd[0] == 0) invert_address(revadd, sender_host_address); - frc = string_format(query, sizeof(query), "%s%s", revadd, domain); - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; - } - - rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask, - invert_result, defer_return); - + rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd, + iplist, bitmask, invert_result, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - sender_host_address, domain); + sender_host_address, dnslist_domain); } - if (rc != FAIL) return rc; /* OK or DEFER */ } @@ -2900,36 +2957,27 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL BOOL defer = FALSE; uschar *keydomain; uschar keybuffer[256]; + uschar keyrevadd[128]; while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, sizeof(keybuffer))) != NULL) { + uschar *prepend = keydomain; + if (string_is_ip_address(keydomain, NULL) != 0) { - uschar keyrevadd[128]; invert_address(keyrevadd, keydomain); - frc = string_format(query, sizeof(query), "%s%s", keyrevadd, domain); - } - else - { - frc = string_format(query, sizeof(query), "%s.%s", keydomain, domain); - } - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; + prepend = keyrevadd; } - rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask, - invert_result, defer_return); + rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, + bitmask, invert_result, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - keydomain, domain); + keydomain, dnslist_domain); return OK; } diff --git a/test/confs/0139 b/test/confs/0139 index 3c4db8535..cd9433a92 100644 --- a/test/confs/0139 +++ b/test/confs/0139 @@ -11,6 +11,7 @@ gecos_name = CALLER_NAME # ----- Main settings ----- domainlist local_domains = exim.test.ex +trusted_users = CALLER acl_smtp_rcpt = check_recipient acl_smtp_mail = check_mail @@ -23,6 +24,9 @@ check_mail: warn dnslists = rbl4.test.ex&0.0.0.6 warn dnslists = rbl4.test.ex&127.0.0.3 warn dnslists = rbl4.test.ex!&0.0.0.7 + add_header = DNSlist: $dnslist_domain $dnslist_text + warn dnslists = rbl5.test.ex,rbl4.test.ex=127.0.0.128 + add_header = DNSlist: $dnslist_domain $dnslist_text accept check_recipient: diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex index 8ac35367e..b269d5856 100644 --- a/test/dnszones-src/db.test.ex +++ b/test/dnszones-src/db.test.ex @@ -1,4 +1,4 @@ -; $Cambridge: exim/test/dnszones-src/db.test.ex,v 1.4 2006/04/18 11:13:19 ph10 Exp $ +; $Cambridge: exim/test/dnszones-src/db.test.ex,v 1.5 2006/10/03 15:11:22 ph10 Exp $ ; This is a testing zone file for use when testing DNS handling in Exim. This ; is a fake zone of no real use - hence no SOA record. The zone name is @@ -176,6 +176,11 @@ recurse.test.ex A V4NET.99.0.2 20.12.11.V4NET.rbl4 A 127.0.0.6 21.12.11.V4NET.rbl4 A 127.0.0.7 +22.12.11.V4NET.rbl4 A 127.0.0.128 + TXT "This is a test blacklisting4 message" + +22.12.11.V4NET.rbl5 A 127.0.0.1 + TXT "This is a test blacklisting5 message" 1.13.13.V4NET.rbl CNAME non-exist.test.ex. 2.13.13.V4NET.rbl A 127.0.0.1 diff --git a/test/log/0139 b/test/log/0139 new file mode 100644 index 000000000..c6f94cb9f --- /dev/null +++ b/test/log/0139 @@ -0,0 +1,3 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= postmaster@exim.test.ex H=[V4NET.11.12.22] U=CALLER P=smtp S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => userx R=localuser T=local_delivery +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed diff --git a/test/mail/0139.userx b/test/mail/0139.userx new file mode 100644 index 000000000..f527841d2 --- /dev/null +++ b/test/mail/0139.userx @@ -0,0 +1,14 @@ +From postmaster@exim.test.ex Tue Mar 02 09:44:33 1999 +Return-path: +Envelope-to: userx@exim.test.ex +Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000 +Received: from [V4NET.11.12.22] (ident=CALLER) + by the.local.host.name with smtp (Exim x.yz) + (envelope-from ) + id 10HmaX-0005vi-00 + for userx@exim.test.ex; Tue, 2 Mar 1999 09:44:33 +0000 +DNSlist: rbl4.test.ex This is a test blacklisting4 message +DNSlist: rbl5.test.ex This is a test blacklisting5 message + +test data + diff --git a/test/scripts/0000-Basic/0139 b/test/scripts/0000-Basic/0139 index 6d613a7ca..4cd168e18 100644 --- a/test/scripts/0000-Basic/0139 +++ b/test/scripts/0000-Basic/0139 @@ -24,4 +24,12 @@ exim -bh V4NET.11.12.21 mail from: quit **** +exim -odi -bs -oMa V4NET.11.12.22 +mail from: +rcpt to: +data +test data +. +quit +**** no_msglog_check diff --git a/test/stderr/0139 b/test/stderr/0139 index 011047452..14cdd0647 100644 --- a/test/stderr/0139 +++ b/test/stderr/0139 @@ -28,6 +28,13 @@ >>> DNS lookup for 14.12.11.V4NET.rbl4.test.ex failed >>> => that means V4NET.11.12.14 is not listed at rbl4.test.ex >>> warn: condition test failed +>>> processing "warn" +>>> check dnslists = rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> DNS list check: rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> using result of previous DNS lookup +>>> DNS lookup for 14.12.11.V4NET.rbl4.test.ex failed +>>> => that means V4NET.11.12.14 is not listed at rbl4.test.ex +>>> warn: condition test failed >>> processing "accept" >>> accept: condition test succeeded >>> using ACL "check_recipient" @@ -111,7 +118,7 @@ >>> exim.test.ex in "+local_domains"? yes (matched "+local_domains") >>> accept: condition test succeeded >>> host in ignore_fromline_hosts? no (option unset) -LOG: 10HmaX-0005vi-00 <= postmaster@exim.test.ex H=[V4NET.11.12.14] P=smtp S=sss +LOG: 10HmaY-0005vi-00 <= postmaster@exim.test.ex H=[V4NET.11.12.14] P=smtp S=sss >>> host in hosts_connection_nolog? no (option unset) >>> host in host_lookup? no (option unset) >>> host in host_reject_connection? no (option unset) @@ -142,6 +149,13 @@ LOG: 10HmaX-0005vi-00 <= postmaster@exim.test.ex H=[V4NET.11.12.14] P=smtp S=sss >>> DNS lookup for 15.12.11.V4NET.rbl4.test.ex failed >>> => that means V4NET.11.12.15 is not listed at rbl4.test.ex >>> warn: condition test failed +>>> processing "warn" +>>> check dnslists = rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> DNS list check: rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> using result of previous DNS lookup +>>> DNS lookup for 15.12.11.V4NET.rbl4.test.ex failed +>>> => that means V4NET.11.12.15 is not listed at rbl4.test.ex +>>> warn: condition test failed >>> processing "accept" >>> accept: condition test succeeded >>> using ACL "check_recipient" @@ -191,7 +205,17 @@ LOG: H=[V4NET.11.12.15] F= rejected RCPT >> using result of previous DNS lookup >>> DNS lookup for 20.12.11.V4NET.rbl4.test.ex succeeded (yielding 127.0.0.6) >>> => that means V4NET.11.12.20 is listed at rbl4.test.ex +>>> check add_header = DNSlist: $dnslist_domain $dnslist_text +>>> = DNSlist: rbl4.test.ex >>> warn: condition test succeeded +>>> processing "warn" +>>> check dnslists = rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> DNS list check: rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> using result of previous DNS lookup +>>> DNS lookup for 20.12.11.V4NET.rbl4.test.ex succeeded (yielding 127.0.0.6) +>>> => but we are not accepting this block class because +>>> => there was no match for =127.0.0.128 +>>> warn: condition test failed >>> processing "accept" >>> accept: condition test succeeded >>> host in hosts_connection_nolog? no (option unset) @@ -225,5 +249,13 @@ LOG: H=[V4NET.11.12.15] F= rejected RCPT >> => but we are not accepting this block class because >>> => there was an exclude match for &0.0.0.7 >>> warn: condition test failed +>>> processing "warn" +>>> check dnslists = rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> DNS list check: rbl5.test.ex,rbl4.test.ex=127.0.0.128 +>>> using result of previous DNS lookup +>>> DNS lookup for 21.12.11.V4NET.rbl4.test.ex succeeded (yielding 127.0.0.7) +>>> => but we are not accepting this block class because +>>> => there was no match for =127.0.0.128 +>>> warn: condition test failed >>> processing "accept" >>> accept: condition test succeeded diff --git a/test/stdout/0139 b/test/stdout/0139 index 748852cce..011f2d4fb 100644 --- a/test/stdout/0139 +++ b/test/stdout/0139 @@ -8,7 +8,7 @@ 250 Accepted 250 Accepted 354 Enter message, ending with "." on a line by itself -250 OK id=10HmaX-0005vi-00 +250 OK id=10HmaY-0005vi-00 **** SMTP testing: that is not a real message id! @@ -44,3 +44,9 @@ 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 250 OK 221 the.local.host.name closing connection +220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250 OK +250 Accepted +354 Enter message, ending with "." on a line by itself +250 OK id=10HmaX-0005vi-00 +221 the.local.host.name closing connection -- 2.30.2