Michael Deutschmann's patch for getting TXT from a specific list when
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 3 Oct 2006 15:11:22 +0000 (15:11 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 3 Oct 2006 15:11:22 +0000 (15:11 +0000)
a match is found on a merged list.

doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/ACKNOWLEDGMENTS
src/src/verify.c
test/confs/0139
test/dnszones-src/db.test.ex
test/log/0139 [new file with mode: 0644]
test/mail/0139.userx [new file with mode: 0644]
test/scripts/0000-Basic/0139
test/stderr/0139
test/stdout/0139

index 111d2ae..e7d4a3f 100644 (file)
@@ -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
 -----------------
index 8a50941..4ee55fd 100644 (file)
@@ -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
index 0a92594..d5c19bc 100644 (file)
@@ -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}
index d180fda..4d0fe69 100644 (file)
@@ -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;
         }
 
index 3c4db85..cd9433a 100644 (file)
@@ -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:
index 8ac3536..b269d58 100644 (file)
@@ -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 (file)
index 0000000..c6f94cb
--- /dev/null
@@ -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 <userx@exim.test.ex> 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 (file)
index 0000000..f527841
--- /dev/null
@@ -0,0 +1,14 @@
+From postmaster@exim.test.ex Tue Mar 02 09:44:33 1999
+Return-path: <postmaster@exim.test.ex>
+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 <postmaster@exim.test.ex>)
+       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
+
index 6d613a7..4cd168e 100644 (file)
@@ -24,4 +24,12 @@ exim -bh V4NET.11.12.21
 mail from:<postmaster@exim.test.ex>
 quit
 ****
+exim -odi -bs -oMa V4NET.11.12.22
+mail from:<postmaster@exim.test.ex>
+rcpt to:<userx@exim.test.ex>
+data
+test data
+.
+quit
+****
 no_msglog_check
index 0110474..14cdd06 100644 (file)
 >>> 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"
 >>> 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=<postmaster@exim.test.ex> rejected RCPT <userx@exim.te
 >>> 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=<postmaster@exim.test.ex> rejected RCPT <userx@exim.te
 >>> => 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
index 748852c..011f2d4 100644 (file)
@@ -8,7 +8,7 @@
 250 Accepted\r
 250 Accepted\r
 354 Enter message, ending with "." on a line by itself\r
-250 OK id=10HmaX-0005vi-00\r
+250 OK id=10HmaY-0005vi-00\r
 
 **** 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\r
 250 OK\r
 221 the.local.host.name closing connection\r
+220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaX-0005vi-00\r
+221 the.local.host.name closing connection\r