defer_ok was not working if used on a callout with verify=header_sender.
[exim.git] / src / src / verify.c
index 45a4c81..dedc39f 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.3 2004/11/05 16:53:28 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.14 2005/01/27 10:26:14 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -377,6 +377,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
   smtp_outblock outblock;
   int host_af;
   int port = 25;
+  BOOL send_quit = TRUE; 
   uschar *helo = US"HELO";
   uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
   uschar inbuffer[4096];
@@ -609,6 +610,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
     if (errno == ETIMEDOUT)
       {
       HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+      send_quit = FALSE; 
       }
     else if (errno == 0)
       {
@@ -637,7 +639,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
 
   /* End the SMTP conversation and close the connection. */
 
-  (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+  if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
   close(inblock.sock);
   }    /* Loop through all hosts, while !done */
 
@@ -1037,15 +1039,20 @@ while (addr_new != NULL)
           else
             {
             uschar *canonical_name;
-            host_item *host;
+            host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
 
             /* Just ignore failures to find a host address. If we don't manage
-            to find any addresses, the callout will defer. */
+            to find any addresses, the callout will defer. Note that more than 
+            one address may be found for a single host, which will result in 
+            additional host items being inserted into the chain. Hence we must 
+            save the next host first. */
 
-            for (host = host_list; host != NULL; host = host->next)
+            for (host = host_list; host != NULL; host = nexthost)
               {
-              if (tf.gethostbyname || string_is_ip_address(host->name, NULL))
+              nexthost = host->next;
+              if (tf.gethostbyname || 
+                  string_is_ip_address(host->name, NULL) > 0)
                 (void)host_find_byname(host, NULL, &canonical_name, TRUE);
               else
                 {
@@ -1095,7 +1102,7 @@ while (addr_new != NULL)
   want to continue to verify the new child. */
 
   if (rc == REROUTED) continue;
-
+  
   /* Handle hard failures */
 
   if (rc == FAIL)
@@ -1458,6 +1465,7 @@ Arguments:
   se_mailfrom      mailfrom for verify; NULL => ""
   pm_mailfrom      sender for pm callout check (passed to verify_address())
   options          callout options (passed to verify_address())
+  verrno           where to put the address basic_errno 
 
 If log_msgptr is set to something without setting user_msgptr, the caller
 normally uses log_msgptr for both things.
@@ -1469,7 +1477,7 @@ Returns:           result of the verification attempt: OK, FAIL, or DEFER;
 int
 verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr,
   int callout, int callout_overall, int callout_connect, uschar *se_mailfrom, 
-  uschar *pm_mailfrom, int options)
+  uschar *pm_mailfrom, int options, int *verrno)
 {
 static int header_types[] = { htype_sender, htype_reply_to, htype_from };
 int yield = FAIL;
@@ -1550,7 +1558,7 @@ for (i = 0; i < 3; i++)
             }
           }
 
-        /* Else go ahead with the sender verification. But is isn't *the*
+        /* Else go ahead with the sender verification. But it isn't *the*
         sender of the message, so set vopt_fake_sender to stop sender_address
         being replaced after rewriting or qualification. */
 
@@ -1568,12 +1576,16 @@ for (i = 0; i < 3; i++)
       last of these will be returned to the user if all three fail. We do not
       set a log message - the generic one below will be used. */
 
-      if (new_ok != OK && smtp_return_error_details)
+      if (new_ok != OK)
         {
-        *user_msgptr = string_sprintf("Rejected after DATA: "
-          "could not verify \"%.*s\" header address\n%s: %s",
-          endname - h->text, h->text, vaddr->address, vaddr->message);
-        }
+        *verrno = vaddr->basic_errno; 
+        if (smtp_return_error_details)
+          {
+          *user_msgptr = string_sprintf("Rejected after DATA: "
+            "could not verify \"%.*s\" header address\n%s: %s",
+            endname - h->text, h->text, vaddr->address, vaddr->message);
+          }
+        }   
 
       /* Success or defer */
 
@@ -1826,7 +1838,7 @@ if (*ss == '@')
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
 a (possibly masked) comparision with the current IP address. */
 
-if (string_is_ip_address(ss, &maskoffset))
+if (string_is_ip_address(ss, &maskoffset) > 0)
   return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL);
 
 /* If the item is of the form net[n]-lookup;<file|query> then it is a lookup on
@@ -1862,7 +1874,8 @@ if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != NULL)
     /* Adjust parameters for the type of lookup. For a query-style
     lookup, there is no file name, and the "key" is just the query. For
     a single-key lookup, the key is the current IP address, masked
-    appropriately, and reconverted to text form, with the mask appended. */
+    appropriately, and reconverted to text form, with the mask appended. 
+    For IPv6 addresses, specify dot separators instead of colons. */
 
     if (mac_islookup(search_type, lookup_querystyle))
       {
@@ -1873,7 +1886,7 @@ if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != NULL)
       {
       insize = host_aton(cb->host_address, incoming);
       host_mask(insize, incoming, mlen);
-      (void)host_nmtoa(insize, incoming, mlen, buffer);
+      (void)host_nmtoa(insize, incoming, mlen, buffer, '.');
       key = buffer;
       filename = semicolon + 1;
       }
@@ -2050,7 +2063,9 @@ int
 verify_check_this_host(uschar **listptr, unsigned int *cache_bits,
   uschar *host_name, uschar *host_address, uschar **valueptr)
 {
+int rc;
 unsigned int *local_cache_bits = cache_bits;
+uschar *save_host_address = deliver_host_address;
 check_host_block cb;
 cb.host_name = host_name;
 cb.host_address = host_address;
@@ -2064,9 +2079,26 @@ addresses. */
 cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
   host_address + 7 : host_address;
 
-return match_check_list(listptr, 0, &hostlist_anchor, &local_cache_bits,
-  check_host, &cb, MCL_HOST,
-  (host_address == sender_host_address)? US"host" : host_address, valueptr);
+/* During the running of the check, put the IP address into $host_address. In 
+the case of calls from the smtp transport, it will already be there. However, 
+in other calls (e.g. when testing ignore_target_hosts), it won't. Just to be on 
+the safe side, any existing setting is preserved, though as I write this
+(November 2004) I can't see any cases where it is actually needed. */
+
+deliver_host_address = host_address;
+rc = match_check_list(
+       listptr,                                /* the list */
+       0,                                      /* separator character */
+       &hostlist_anchor,                       /* anchor pointer */
+       &local_cache_bits,                      /* cache pointer */
+       check_host,                             /* function for testing */
+       &cb,                                    /* argument for function */
+       MCL_HOST,                               /* type of check */
+       (host_address == sender_host_address)? 
+         US"host" : host_address,              /* text for debugging */
+       valueptr);                              /* where to pass back data */
+deliver_host_address = save_host_address;
+return rc; 
 }
 
 
@@ -2160,6 +2192,254 @@ else
 
 
 /*************************************************
+*          Perform a single dnsbl lookup         *
+*************************************************/
+
+/* This function is called from verify_check_dnsbl() below.
+
+Arguments:
+  domain         the outer dnsbl domain (for debug message)
+  keydomain      the current keydomain (for debug message) 
+  query          the domain to be looked up
+  iplist         the list of matching IP addresses 
+  bitmask        true if bitmask matching is wanted 
+  invert_result  true if result to be inverted 
+  defer_return   what to return for a defer 
+
+Returns:         OK if lookup succeeded
+                 FAIL if not
+*/
+
+static int
+one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query, 
+  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;
+
+/* Look for this query in the cache. */
+
+t = tree_search(dnsbl_cache, query);
+
+/* If not cached from a previous lookup, we must do a DNS lookup, and
+cache the result in permanent memory. */
+
+if (t == NULL)
+  {
+  store_pool = POOL_PERM;
+
+  /* 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 . */
+
+  HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
+  cb->rc = dns_basic_lookup(&dnsa, query, T_A);
+  cb->text_set = FALSE;
+  cb->text = NULL;
+  cb->rhs = NULL;
+
+  /* If the lookup succeeded, cache the RHS address. The code allows for
+  more than one address - this was for complete generality and the possible
+  use of A6 records. However, A6 records have been reduced to experimental
+  status (August 2001) and may die out. So they may never get used at all,
+  let alone in dnsbl records. However, leave the code here, just in case.
+
+  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. */
+
+  if (cb->rc == DNS_SUCCEED)
+    {
+    dns_record *rr;
+    dns_address **addrp = &(cb->rhs);
+    for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+         rr != NULL;
+         rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+      {
+      if (rr->type == T_A)
+        {
+        dns_address *da = dns_address_from_rr(&dnsa, rr);
+        if (da != NULL)
+          {
+          *addrp = da;
+          while (da->next != NULL) da = da->next;
+          addrp = &(da->next);
+          }
+        }
+      }
+
+    /* If we didn't find any A records, change the return code. This can
+    happen when there is a CNAME record but there are no A records for what
+    it points to. */
+
+    if (cb->rhs == NULL) cb->rc = DNS_NODATA;
+    }
+
+  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
+"="), a negative equality list (introduced by "!="), a positive bitmask
+list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
+
+if (cb->rc == DNS_SUCCEED)
+  {
+  dns_address *da = NULL;
+  uschar *addlist = cb->rhs->address;
+
+  /* For A and AAAA records, there may be multiple addresses from multiple
+  records. For A6 records (currently not expected to be used) there may be
+  multiple addresses from a single record. */
+
+  for (da = cb->rhs->next; da != NULL; da = da->next)
+    addlist = string_sprintf("%s, %s", addlist, da->address);
+
+  HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
+    query, addlist);
+
+  /* Address list check; this can be either for equality, or via a bitmask.
+  In the latter case, all the bits must match. */
+
+  if (iplist != NULL)
+    {
+    int ipsep = ',';
+    uschar ip[46];
+    uschar *ptr = iplist;
+
+    while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL)
+      {
+      /* Handle exact matching */
+      if (!bitmask)
+        {
+        for (da = cb->rhs; da != NULL; da = da->next)
+          {
+          if (Ustrcmp(CS da->address, ip) == 0) break;
+          }
+        }
+      /* Handle bitmask matching */
+      else
+        {
+        int address[4];
+        int mask = 0;
+
+        /* At present, all known DNS blocking lists use A records, with
+        IPv4 addresses on the RHS encoding the information they return. I
+        wonder if this will linger on as the last vestige of IPv4 when IPv6
+        is ubiquitous? Anyway, for now we use paranoia code to completely
+        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];
+
+        /* Scan the returned addresses, skipping any that are IPv6 */
+
+        for (da = cb->rhs; da != NULL; da = da->next)
+          {
+          if (host_aton(da->address, address) != 1) continue;
+          if ((address[0] & mask) == mask) break;
+          }
+        }
+
+      /* Break out if a match has been found */
+
+      if (da != NULL) break;
+      }
+
+    /* If either
+
+       (a) No IP address in a positive list matched, or
+       (b) An IP address in a negative list did match
+
+    then behave as if the DNSBL lookup had not succeeded, i.e. the host is
+    not on the list. */
+
+    if (invert_result != (da == NULL))
+      {
+      HDEBUG(D_dnsbl)
+        {
+        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);
+        }
+      return FAIL; 
+      }
+    }
+
+  /* Either there was no IP list, or the record matched. Look up a TXT record
+  if it hasn't previously been done. */
+
+  if (!cb->text_set)
+    {
+    cb->text_set = TRUE;
+    if (dns_basic_lookup(&dnsa, query, T_TXT) == DNS_SUCCEED)
+      {
+      dns_record *rr;
+      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+           rr != NULL;
+           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+        if (rr->type == T_TXT) break;
+      if (rr != NULL)
+        {
+        int len = (rr->data)[0];
+        if (len > 511) len = 127;
+        store_pool = POOL_PERM;
+        cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
+        store_pool = old_pool;
+        }
+      }
+    }
+
+  dnslist_value = addlist;
+  dnslist_text = cb->text;
+  return OK;
+  }
+
+/* There was a problem with the DNS lookup */
+
+if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
+  {
+  log_write(L_dnslist_defer, LOG_MAIN,
+    "DNS list lookup defer (probably timeout) for %s: %s", query,
+    (defer_return == OK)?   US"assumed in list" :
+    (defer_return == FAIL)? US"assumed not in list" :
+                            US"returned DEFER");
+  return defer_return;
+  }
+
+/* No entry was found in the DNS; continue for next domain */
+
+HDEBUG(D_dnsbl)
+  {
+  debug_printf("DNS lookup for %s failed\n", query);
+  debug_printf("=> that means %s is not listed at %s\n",
+     keydomain, domain);
+  }
+
+return FAIL;
+}
+
+
+
+
+/*************************************************
 *        Check host against DNS black lists      *
 *************************************************/
 
@@ -2203,7 +2483,6 @@ verify_check_dnsbl(uschar **listptr)
 {
 int sep = 0;
 int defer_return = FAIL;
-int old_pool = store_pool;
 BOOL invert_result = FALSE;
 uschar *list = *listptr;
 uschar *domain;
@@ -2216,18 +2495,19 @@ uschar revadd[128];        /* Long enough for IPv6 address */
 
 revadd[0] = 0;
 
+/* In case this is the first time the DNS resolver is being used. */
+
+dns_init(FALSE, FALSE);
+
 /* Loop through all the domains supplied, until something matches */
 
 while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   {
+  int rc;
   BOOL frc;
   BOOL bitmask = FALSE;
-  dns_answer dnsa;
-  dns_scan dnss;
   uschar *iplist;
   uschar *key;
-  tree_node *t;
-  dnsbl_cache_block *cb;
 
   HDEBUG(D_dnsbl) debug_printf("DNS list check: %s\n", domain);
 
@@ -2286,251 +2566,87 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
       }
     }
 
-  /* Construct the query by adding the domain onto either the sending host
-  address, or the given key string. */
-
+  /* 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. */
+  
   if (key == 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);
-    }
-  else
-    {
-    frc = string_format(query, sizeof(query), "%s.%s", key, domain);
-    }
-
-  if (!frc)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
-      "(ignored): %s...", query);
-    continue;
-    }
-
-  /* Look for this query in the cache. */
-
-  t = tree_search(dnsbl_cache, query);
-
-  /* If not cached from a previous lookup, we must do a DNS lookup, and
-  cache the result in permanent memory. */
-
-  if (t == NULL)
-    {
-    store_pool = POOL_PERM;
-
-    /* In case this is the first time the DNS resolver is being used. */
-
-    dns_init(FALSE, FALSE);
-
-    /* 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 . */
-
-    HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
-    cb->rc = dns_basic_lookup(&dnsa, query, T_A);
-    cb->text_set = FALSE;
-    cb->text = NULL;
-    cb->rhs = NULL;
-
-    /* If the lookup succeeded, cache the RHS address. The code allows for
-    more than one address - this was for complete generality and the possible
-    use of A6 records. However, A6 records have been reduced to experimental
-    status (August 2001) and may die out. So they may never get used at all,
-    let alone in dnsbl records. However, leave the code here, just in case.
-
-    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. */
-
-    if (cb->rc == DNS_SUCCEED)
+    
+    if (!frc)
       {
-      dns_record *rr;
-      dns_address **addrp = &(cb->rhs);
-      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr != NULL;
-           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-        {
-        if (rr->type == T_A)
-          {
-          dns_address *da = dns_address_from_rr(&dnsa, rr);
-          if (da != NULL)
-            {
-            *addrp = da;
-            while (da->next != NULL) da = da->next;
-            addrp = &(da->next);
-            }
-          }
-        }
-
-      /* If we didn't find any A records, change the return code. This can
-      happen when there is a CNAME record but there are no A records for what
-      it points to. */
-
-      if (cb->rhs == NULL) cb->rc = DNS_NODATA;
+      log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+        "(ignored): %s...", query);
+      continue;
       }
-
-    store_pool = old_pool;
+      
+    rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask, 
+      invert_result, defer_return);
+       
+    if (rc == OK)
+      {
+      dnslist_domain = string_copy(domain);
+      HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", 
+        sender_host_address, domain);
+      }
+       
+    if (rc != FAIL) return rc;     /* OK or DEFER */
     }
-
-  /* Previous lookup was cached */
-
+    
+  /* If there is a key string, it can be a list of domains or IP addresses to 
+  be concatenated with the main domain. */
   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
-  "="), a negative equality list (introduced by "!="), a positive bitmask
-  list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
-
-  if (cb->rc == DNS_SUCCEED)
-    {
-    dns_address *da = NULL;
-    uschar *addlist = cb->rhs->address;
-
-    /* For A and AAAA records, there may be multiple addresses from multiple
-    records. For A6 records (currently not expected to be used) there may be
-    multiple addresses from a single record. */
-
-    for (da = cb->rhs->next; da != NULL; da = da->next)
-      addlist = string_sprintf("%s, %s", addlist, da->address);
-
-    HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
-      query, addlist);
-
-    /* Address list check; this can be either for equality, or via a bitmask.
-    In the latter case, all the bits must match. */
-
-    if (iplist != NULL)
-      {
-      int ipsep = ',';
-      uschar ip[46];
-      uschar *ptr = iplist;
-
-      while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL)
+    int keysep = 0;
+    BOOL defer = FALSE; 
+    uschar *keydomain; 
+    uschar keybuffer[256];
+  
+    while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, 
+            sizeof(keybuffer))) != NULL)
+      {       
+      if (string_is_ip_address(keydomain, NULL) > 0)
         {
-        /* Handle exact matching */
-        if (!bitmask)
-          {
-          for (da = cb->rhs; da != NULL; da = da->next)
-            {
-            if (Ustrcmp(CS da->address, ip) == 0) break;
-            }
-          }
-        /* Handle bitmask matching */
-        else
-          {
-          int address[4];
-          int mask = 0;
-
-          /* At present, all known DNS blocking lists use A records, with
-          IPv4 addresses on the RHS encoding the information they return. I
-          wonder if this will linger on as the last vestige of IPv4 when IPv6
-          is ubiquitous? Anyway, for now we use paranoia code to completely
-          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];
-
-          /* Scan the returned addresses, skipping any that are IPv6 */
-
-          for (da = cb->rhs; da != NULL; da = da->next)
-            {
-            if (host_aton(da->address, address) != 1) continue;
-            if ((address[0] & mask) == mask) break;
-            }
-          }
-
-        /* Break out if a match has been found */
-
-        if (da != NULL) break;
+        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 either
-
-         (a) No IP address in a positive list matched, or
-         (b) An IP address in a negative list did match
-
-      then behave as if the DNSBL lookup had not succeeded, i.e. the host is
-      not on the list. */
-
-      if (invert_result != (da == NULL))
+      if (!frc)
         {
-        HDEBUG(D_dnsbl)
-          {
-          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);
-          }
-        continue;   /* With next DNSBL domain */
+        log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+          "(ignored): %s...", query);
+        continue;
         }
-      }
-
-  /* Either there was no IP list, or the record matched. Look up a TXT record
-    if it hasn't previously been done. */
-
-    if (!cb->text_set)
-      {
-      cb->text_set = TRUE;
-      if (dns_basic_lookup(&dnsa, query, T_TXT) == DNS_SUCCEED)
+        
+      rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask, 
+        invert_result, defer_return);
+         
+      if (rc == OK)
         {
-        dns_record *rr;
-        for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-             rr != NULL;
-             rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-          if (rr->type == T_TXT) break;
-        if (rr != NULL)
-          {
-          int len = (rr->data)[0];
-          if (len > 511) len = 127;
-          store_pool = POOL_PERM;
-          cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
-          store_pool = old_pool;
-          }
+        dnslist_domain = string_copy(domain);
+        HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", 
+          keydomain, domain);
+        return OK;   
         }
-      }
-
-    HDEBUG(D_dnsbl)
-      {
-      debug_printf("=> that means %s is listed at %s\n",
-        (key == NULL)? sender_host_address : key, domain);
-      }
-
-    dnslist_domain = string_copy(domain);
-    dnslist_value = addlist;
-    dnslist_text = cb->text;
-    return OK;
-    }
+         
+      /* If the lookup deferred, remember this fact. We keep trying the rest
+      of the list to see if we get a useful result, and if we don't, we return
+      DEFER at the end. */
 
-  /* There was a problem with the DNS lookup */
+      if (rc == DEFER) defer = TRUE;
+      }    /* continue with next keystring domain/address */
 
-  if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
-    {
-    log_write(L_dnslist_defer, LOG_MAIN,
-      "DNS list lookup defer (probably timeout) for %s: %s", query,
-      (defer_return == OK)?   US"assumed in list" :
-      (defer_return == FAIL)? US"assumed not in list" :
-                              US"returned DEFER");
-    return defer_return;
-    }
-
-  /* No entry was found in the DNS; continue for next domain */
-
-  HDEBUG(D_dnsbl)
-    {
-    debug_printf("DNS lookup for %s failed\n", query);
-    debug_printf("=> that means %s is not listed at %s\n",
-      (key == NULL)? sender_host_address : key, domain);
-    }
-  }      /* Continue with next domain */
+    if (defer) return DEFER;
+    }  
+  }        /* continue with next dnsdb outer domain */
 
 return FAIL;
 }