Fix negated dnslists item bug; add == and =& features, courtesy Brad
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Wed, 17 Jan 2007 11:17:58 +0000 (11:17 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Wed, 17 Jan 2007 11:17:58 +0000 (11:17 +0000)
Jorsch.

doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/ACKNOWLEDGMENTS
src/src/verify.c
test/confs/0139
test/scripts/0000-Basic/0139
test/stderr/0139
test/stdout/0139

index 83bba99ee0aca5a2c02a42e38db5c78e56083f78..240c815eb657baa41bbc00d6316aabfb0f2a7233 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.453 2007/01/16 21:00:29 magnus Exp $
+$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.454 2007/01/17 11:17:58 ph10 Exp $
 
 Change log file for Exim from version 4.21
 -------------------------------------------
@@ -13,6 +13,19 @@ MH/01 Fix for bug #448, segfault in Dovecot authenticator when interface_address
 PH/01 Added a new log selector smtp_no_mail, to log SMTP sessions that do not
       issue a MAIL command.
 
+PH/02 In an ACL statement such as
+
+        deny dnslists = X!=127.0.0.2 : X=127.0.0.2
+
+      if a client was not listed at all, or was listed with a value other than
+      127.0.0.2, in the X list, but was listed with 127.0.0.2 in the Y list,
+      the condition was not true (as it should be), so access was not denied.
+      The bug was that the ! inversion was incorrectly passed on to the second
+      item. This has been fixed.
+
+PH/03 Added additional dnslists conditions == and =& which are different from
+      = and & when the dns lookup returns more than one IP address.
+
 
 Exim version 4.66
 -----------------
index a24a21226a6d91c638c524913238983ba1e7ae7c..960f93ce80a124205befccd7409c6aefb4067426 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/NewStuff,v 1.126 2007/01/15 15:59:22 ph10 Exp $
+$Cambridge: exim/doc/doc-txt/NewStuff,v 1.127 2007/01/17 11:17:58 ph10 Exp $
 
 New Features in Exim
 --------------------
@@ -38,6 +38,74 @@ Version 4.67
     setting of 10 for smtp_accep_max_nonmail, the connection will in any case
     be aborted before 20 non-mail commands are processed.
 
+ 2. When an item in a dnslists list is followed by = and & and a list of IP
+    addresses, in order to restrict the match to specific results from the DNS
+    lookup, the behaviour was not clear when the lookup returned more than one
+    IP address. For example, consider the condition
+
+      dnslists = a.b.c=127.0.0.1
+
+    What happens if the DNS lookup for the incoming IP address yields both
+    127.0.0.1 and 127.0.0.2 by means of two separate DNS records? Is the
+    condition true because at least one given value was found, or is it false
+    because at least one of the found values was not listed? And how does this
+    affect negated conditions?
+
+    The behaviour of = and & has not been changed; however, the text below
+    documents it more clearly. In addition, two new additional conditions (==
+    and =&) have been added, to permit the "other" behaviour to be configured.
+
+    A DNS lookup may yield more than one record. Thus, the result of the lookup
+    for a dnslists check may yield more than one IP address. The question then
+    arises as to whether all the looked up addresses must be listed, or whether
+    just one is good enough. Both possibilities are provided for:
+
+    . If = or & is used, the condition is true if any one of the looked up
+      IP addresses matches one of the listed addresses. Consider:
+
+        dnslists = a.b.c=127.0.0.1
+
+      If the DNS lookup yields both 127.0.0.1 and 127.0.0.2, the condition is
+      true because 127.0.0.1 matches.
+
+    . If == or =& is used, the condition is true only if every one of the
+      looked up IP addresses matches one of the listed addresses. Consider:
+
+        dnslists = a.b.c==127.0.0.1
+
+      If the DNS lookup yields both 127.0.0.1 and 127.0.0.2, the condition is
+      false because 127.0.0.2 is not listed. You would need to have
+
+        dnslists = a.b.c==127.0.0.1,127.0.0.2
+
+      for the condition to be true.
+
+    When ! is used to negate IP address matching, it inverts the result, giving
+    the precise opposite of the behaviour above. Thus:
+
+    . If != or !& is used, the condition is true if none of the looked up IP
+      addresses matches one of the listed addresses. Consider:
+
+        dnslists = a.b.c!&0.0.0.1
+
+      If the DNS lookup yields both 127.0.0.1 and 127.0.0.2, the condition is
+      false because 127.0.0.1 matches.
+
+    . If !== or !=& is used, the condition is true there is at least one looked
+      up IP address that does not match. Consider:
+
+        dnslists = a.b.c!=&0.0.0.1
+
+      If the DNS lookup yields both 127.0.0.1 and 127.0.0.2, the condition is
+      true, because 127.0.0.2 does not match. You would need to have
+
+        dnslists = a.b.c!=&0.0.0.1,0.0.0.2
+
+      for the condition to be false.
+
+    When the DNS lookup yields only a single IP address, there is no difference
+    between = and == and between & and =&.
+
 
 Version 4.66
 ------------
index 08440283ac5e705ea686b617a363585bede34478..a4b6016525053747cb17435d11ea2b16c9c98a71 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.67 2006/12/19 14:51:34 ph10 Exp $
+$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.68 2007/01/17 11:17:58 ph10 Exp $
 
 EXIM ACKNOWLEDGEMENTS
 
@@ -20,7 +20,7 @@ relatively small patches.
 Philip Hazel
 
 Lists created: 20 November 2002
-Last updated:  19 December 2006
+Last updated:  17 January 2007
 
 
 THE OLD LIST
@@ -178,6 +178,7 @@ Bob Johannessen           Patch for Sieve envelope tests bug
                           Patch for negative uid/gid bug
 Brad Jorsch               Patch for bitwise logical operators
                           Patch for using "message" on acceptance
+                          Patch to add == and =& to dnslists
 Christian Kellner         Patch for LDAP dereferencing
 Alex Kiernan              Patches for libradius
                           Diagnosis of milliwait clock-backwards bug
index 141446aa7d5a7bbc288bd286d0aca7047a357ba1..3b0983919b44238f8e8180f735f85aae73938758 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.45 2007/01/08 10:50:18 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.46 2007/01/17 11:17:58 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -29,6 +29,12 @@ typedef struct dnsbl_cache_block {
 static tree_node *dnsbl_cache = NULL;
 
 
+/* Bits for match_type in one_check_dnsbl() */
+
+#define MT_NOT 1
+#define MT_ALL 2
+
+
 
 /*************************************************
 *          Retrieve a callout cache record       *
@@ -2540,7 +2546,12 @@ Arguments:
                    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
+  match_type     condition for 'succeed' result
+                   0 => Any RR in iplist     (=)
+                   1 => No RR in iplist      (!=)
+                   2 => All RRs in iplist    (==)
+                   3 => Some RRs not in iplist (!==)
+                   the two bits are defined as MT_NOT and MT_ALL
   defer_return   what to return for a defer
 
 Returns:         OK if lookup succeeded
@@ -2549,7 +2560,7 @@ Returns:         OK if lookup succeeded
 
 static int
 one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
-  uschar *prepend, uschar *iplist, BOOL bitmask, BOOL invert_result,
+  uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
   int defer_return)
 {
 dns_answer dnsa;
@@ -2668,21 +2679,25 @@ if (cb->rc == DNS_SUCCEED)
 
   if (iplist != NULL)
     {
-    int ipsep = ',';
-    uschar ip[46];
-    uschar *ptr = iplist;
-
-    while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL)
+    for (da = cb->rhs; da != NULL; da = da->next)
       {
+      int ipsep = ',';
+      uschar ip[46];
+      uschar *ptr = iplist;
+      uschar *res;
+
       /* Handle exact matching */
+
       if (!bitmask)
         {
-        for (da = cb->rhs; da != NULL; da = da->next)
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
           {
           if (Ustrcmp(CS da->address, ip) == 0) break;
           }
         }
+
       /* Handle bitmask matching */
+
       else
         {
         int address[4];
@@ -2695,37 +2710,60 @@ if (cb->rc == DNS_SUCCEED)
         ignore IPv6 addresses. The default mask is 0, which always matches.
         We change this only for IPv4 addresses in the list. */
 
-        if (host_aton(ip, address) == 1) mask = address[0];
+        if (host_aton(da->address, address) == 1) mask = address[0];
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
-        for (da = cb->rhs; da != NULL; da = da->next)
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
           {
-          if (host_aton(da->address, address) != 1) continue;
-          if ((address[0] & mask) == mask) break;
+          if (host_aton(ip, address) != 1) continue;
+          if ((address[0] & mask) == address[0]) break;
           }
         }
 
-      /* Break out if a match has been found */
+      /* If either
+
+         (a) An IP address in an any ('=') list matched, or
+         (b) No IP address in an all ('==') list matched
 
-      if (da != NULL) break;
+      then we're done searching. */
+
+      if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
       }
 
-    /* If either
+    /* If da == NULL, either
 
-       (a) No IP address in a positive list matched, or
-       (b) An IP address in a negative list did match
+       (a) No IP address in an any ('=') list matched, or
+       (b) An IP address in an all ('==') list didn't match
 
-    then behave as if the DNSBL lookup had not succeeded, i.e. the host is
-    not on the list. */
+    so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
+    the list. */
 
-    if (invert_result != (da == NULL))
+    if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
       {
       HDEBUG(D_dnsbl)
         {
+        uschar *res = NULL;
+        switch(match_type)
+          {
+          case 0:
+          res = US"was no match";
+          break;
+          case MT_NOT:
+          res = US"was an exclude match";
+          break;
+          case MT_ALL:
+          res = US"was an IP address that did not match";
+          break;
+          case MT_NOT|MT_ALL:
+          res = US"were no IP addresses that did not match";
+          break;
+          }
         debug_printf("=> but we are not accepting this block class because\n");
-        debug_printf("=> there was %s match for %c%s\n",
-          invert_result? "an exclude":"no", bitmask? '&' : '=', iplist);
+        debug_printf("=> there %s for %s%c%s\n",
+          res,
+          ((match_type & MT_ALL) == 0)? "" : "=",
+          bitmask? '&' : '=', iplist);
         }
       return FAIL;
       }
@@ -2739,7 +2777,7 @@ if (cb->rc == DNS_SUCCEED)
 
   if (domain_txt != domain)
     return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
-      FALSE, invert_result, defer_return);
+      FALSE, match_type, defer_return);
 
   /* If there is no alternate domain, look up a TXT record in the main domain
   if it has not previously been cached. */
@@ -2852,7 +2890,6 @@ verify_check_dnsbl(uschar **listptr)
 {
 int sep = 0;
 int defer_return = FAIL;
-BOOL invert_result = FALSE;
 uschar *list = *listptr;
 uschar *domain;
 uschar *s;
@@ -2873,6 +2910,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
   {
   int rc;
   BOOL bitmask = FALSE;
+  int match_type = 0;
   uschar *domain_txt;
   uschar *comma;
   uschar *iplist;
@@ -2899,8 +2937,8 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
   if (key != NULL) *key++ = 0;
 
   /* See if there's a list of addresses supplied after the domain name. This is
-  introduced by an = or a & character; if preceded by ! we invert the result.
-  */
+  introduced by an = or a & character; if preceded by = we require all matches
+  and if preceded by ! we invert the result. */
 
   iplist = Ustrchr(domain, '=');
   if (iplist == NULL)
@@ -2909,14 +2947,23 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     iplist = Ustrchr(domain, '&');
     }
 
-  if (iplist != NULL)
+  if (iplist != NULL)                          /* Found either = or & */
     {
-    if (iplist > domain && iplist[-1] == '!')
+    if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
       {
-      invert_result = TRUE;
+      match_type |= MT_NOT;
       iplist[-1] = 0;
       }
-    *iplist++ = 0;
+
+    *iplist++ = 0;                             /* Terminate domain, move on */
+
+    /* If we found = (bitmask == FALSE), check for == or =& */
+
+    if (!bitmask && (*iplist == '=' || *iplist == '&'))
+      {
+      bitmask = *iplist++ == '&';
+      match_type |= MT_ALL;
+      }
     }
 
   /* If there is a comma in the domain, it indicates that a second domain for
@@ -2967,7 +3014,7 @@ 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);
     rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
-      iplist, bitmask, invert_result, defer_return);
+      iplist, bitmask, match_type, defer_return);
     if (rc == OK)
       {
       dnslist_domain = string_copy(domain_txt);
@@ -3000,7 +3047,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
         }
 
       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
-        bitmask, invert_result, defer_return);
+        bitmask, match_type, defer_return);
 
       if (rc == OK)
         {
index cd9433a92056c627485f494159a036e25857e4db..2f844b5f019513dfd0fb436ffd3fd1b89fd40298 100644 (file)
@@ -13,13 +13,30 @@ gecos_name = CALLER_NAME
 domainlist local_domains = exim.test.ex
 trusted_users = CALLER
 
+acl_smtp_helo = check_helo
 acl_smtp_rcpt = check_recipient
 acl_smtp_mail = check_mail
+acl_smtp_vrfy = check_vrfy
 
 # ------ ACL ------
 
 begin acl
 
+check_helo:
+  warn    dnslists = rbl2.test.ex!=127.0.0.3 : rbl3.test.ex=127.0.0.3
+  accept
+
+check_vrfy:
+  warn    dnslists = rbl.test.ex=127.0.0.1
+  warn    dnslists = rbl.test.ex!=127.0.0.1
+  warn    dnslists = rbl.test.ex!=127.0.0.3
+  warn    dnslists = rbl.test.ex==127.0.0.1
+  warn    dnslists = rbl.test.ex==127.0.0.1,127.0.0.2
+  warn    dnslists = rbl.test.ex!==127.0.0.1
+  warn    dnslists = rbl.test.ex!==127.0.0.3
+  warn    dnslists = rbl.test.ex!==127.0.0.1,127.0.0.2
+  accept
+
 check_mail:
   warn    dnslists = rbl4.test.ex&0.0.0.6
   warn    dnslists = rbl4.test.ex&127.0.0.3
index 4cd168e18b6868522e874c21346b441282e3ae2b..0224bd9d05e5560fb6135dd06a858d59ac685de2 100644 (file)
@@ -32,4 +32,12 @@ test data
 .
 quit
 ****
+exim -bh V4NET.11.12.15
+helo a.b
+quit
+****
+exim -bh V4NET.13.13.2
+vrfy a@b
+quit
+****
 no_msglog_check
index 14cdd06478bd1a3678d60f1dbb5d85ac4a93937d..42ebd686d9fc93a3153105967d6c0f58313e0db4 100644 (file)
@@ -259,3 +259,104 @@ LOG: H=[V4NET.11.12.15] F=<postmaster@exim.test.ex> rejected RCPT <userx@exim.te
 >>> warn: condition test failed
 >>> processing "accept"
 >>> accept: condition test succeeded
+>>> host in hosts_connection_nolog? no (option unset)
+>>> host in host_lookup? no (option unset)
+>>> host in host_reject_connection? no (option unset)
+>>> host in sender_unqualified_hosts? no (option unset)
+>>> host in recipient_unqualified_hosts? no (option unset)
+>>> host in helo_verify_hosts? no (option unset)
+>>> host in helo_try_verify_hosts? no (option unset)
+>>> host in helo_accept_junk_hosts? no (option unset)
+>>> a.b in helo_lookup_domains? no (end of list)
+>>> using ACL "check_helo"
+>>> processing "warn"
+>>> check dnslists = rbl2.test.ex!=127.0.0.3 : rbl3.test.ex=127.0.0.3
+>>> DNS list check: rbl2.test.ex!=127.0.0.3
+>>> new DNS lookup for 15.12.11.V4NET.rbl2.test.ex
+>>> DNS lookup for 15.12.11.V4NET.rbl2.test.ex failed
+>>> => that means V4NET.11.12.15 is not listed at rbl2.test.ex
+>>> DNS list check: rbl3.test.ex=127.0.0.3
+>>> new DNS lookup for 15.12.11.V4NET.rbl3.test.ex
+>>> DNS lookup for 15.12.11.V4NET.rbl3.test.ex succeeded (yielding 127.0.0.3)
+>>> => that means V4NET.11.12.15 is listed at rbl3.test.ex
+>>> warn: condition test succeeded
+>>> processing "accept"
+>>> accept: condition test succeeded
+>>> host in hosts_connection_nolog? no (option unset)
+>>> host in host_lookup? no (option unset)
+>>> host in host_reject_connection? no (option unset)
+>>> host in sender_unqualified_hosts? no (option unset)
+>>> host in recipient_unqualified_hosts? no (option unset)
+>>> host in helo_verify_hosts? no (option unset)
+>>> host in helo_try_verify_hosts? no (option unset)
+>>> host in helo_accept_junk_hosts? no (option unset)
+>>> host in smtp_accept_max_nonmail_hosts? yes (matched "*")
+>>> using ACL "check_vrfy"
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex=127.0.0.1
+>>> DNS list check: rbl.test.ex=127.0.0.1
+>>> new DNS lookup for 2.13.13.V4NET.rbl.test.ex
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => that means V4NET.13.13.2 is listed at rbl.test.ex
+>>> warn: condition test succeeded
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex!=127.0.0.1
+>>> DNS list check: rbl.test.ex!=127.0.0.1
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => but we are not accepting this block class because
+>>> => there was an exclude match for =127.0.0.1
+>>> warn: condition test failed
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex!=127.0.0.3
+>>> DNS list check: rbl.test.ex!=127.0.0.3
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => that means V4NET.13.13.2 is listed at rbl.test.ex
+>>> warn: condition test succeeded
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex==127.0.0.1
+>>> DNS list check: rbl.test.ex==127.0.0.1
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => but we are not accepting this block class because
+>>> => there was an IP address that did not match for ==127.0.0.1
+>>> warn: condition test failed
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex==127.0.0.1,127.0.0.2
+>>> DNS list check: rbl.test.ex==127.0.0.1,127.0.0.2
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => that means V4NET.13.13.2 is listed at rbl.test.ex
+>>> warn: condition test succeeded
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex!==127.0.0.1
+>>> DNS list check: rbl.test.ex!==127.0.0.1
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => that means V4NET.13.13.2 is listed at rbl.test.ex
+>>> warn: condition test succeeded
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex!==127.0.0.3
+>>> DNS list check: rbl.test.ex!==127.0.0.3
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => that means V4NET.13.13.2 is listed at rbl.test.ex
+>>> warn: condition test succeeded
+>>> processing "warn"
+>>> check dnslists = rbl.test.ex!==127.0.0.1,127.0.0.2
+>>> DNS list check: rbl.test.ex!==127.0.0.1,127.0.0.2
+>>> using result of previous DNS lookup
+>>> DNS lookup for 2.13.13.V4NET.rbl.test.ex succeeded (yielding 127.0.0.1, 127.0.0.2)
+>>> => but we are not accepting this block class because
+>>> => there were no IP addresses that did not match for ==127.0.0.1,127.0.0.2
+>>> warn: condition test failed
+>>> processing "accept"
+>>> accept: condition test succeeded
+>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+>>> routing a@b
+>>> calling system_aliases router
+>>> system_aliases router declined for a@b
+>>> a in "userx"? no (end of list)
+>>> no more routers
+LOG: VRFY failed for a@b H=[V4NET.13.13.2]
index 011f2d4fbc15e20bd2f1b58bfdc275f40af28dee..3736dd4fddd3d885c2841d2bf1086f6b6cbbf494 100644 (file)
 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
+
+**** SMTP testing session as if from host V4NET.11.12.15
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250 the.local.host.name Hello a.b [V4NET.11.12.15]\r
+221 the.local.host.name closing connection\r
+
+**** SMTP testing session as if from host V4NET.13.13.2
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+550 <a@b> Unrouteable address\r
+221 the.local.host.name closing connection\r