Build: Split out dnsbl code
authorJeremy Harris <jgh146exb@wizmail.org>
Mon, 10 Aug 2020 18:51:18 +0000 (19:51 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Mon, 10 Aug 2020 21:31:37 +0000 (22:31 +0100)
src/OS/Makefile-Base
src/scripts/MakeLinks
src/src/dnsbl.c [new file with mode: 0644]
src/src/verify.c

index 9eed7b0b9fae1f24bedc1aa0d48de98e6fdb61c7..77fb3474298a3fa399c0f252395ee6c749785a32 100644 (file)
@@ -484,7 +484,7 @@ OBJ_LOOKUPS = lookups/lf_quote.o lookups/lf_check_file.o lookups/lf_sqlperform.o
 
 OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
-        filtertest.o globals.o dkim.o dkim_transport.o hash.o \
+        filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \
         header.o host.o ip.o log.o lss.o match.o md5.o moan.o \
         os.o parse.o queue.o \
         rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
@@ -774,6 +774,7 @@ debug.o:         $(HDRS) debug.c
 deliver.o:       $(HDRS) transports/smtp.h deliver.c
 directory.o:     $(HDRS) directory.c
 dns.o:           $(HDRS) dns.c
+dnsbl.o:         $(HDRS) dnsbl.c
 enq.o:           $(HDRS) enq.c
 exim.o:          $(HDRS) exim.c
 expand.o:        $(HDRS) expand.c
index 784247d99a98f7003b1e418c5385cf182abe13b3..54ca7fb615b9c3951cbe4401c6c5b64a750b5c7d 100755 (executable)
@@ -100,7 +100,7 @@ for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \
   macros.h mytypes.h osfunctions.h store.h structs.h lookupapi.h sha_ver.h \
   \
   acl.c buildconfig.c base64.c child.c crypt16.c daemon.c dbfn.c debug.c \
-  deliver.c directory.c dns.c drtables.c dummies.c enq.c exim.c \
+  deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c exim.c \
   exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c \
   globals.c hash.c header.c host.c ip.c log.c lss.c match.c md5.c moan.c \
   parse.c perl.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \
diff --git a/src/src/dnsbl.c b/src/src/dnsbl.c
new file mode 100644 (file)
index 0000000..d3afd5c
--- /dev/null
@@ -0,0 +1,604 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Functions concerned with dnsbls */
+
+
+#include "exim.h"
+
+/* Structure for caching DNSBL lookups */
+
+typedef struct dnsbl_cache_block {
+  time_t expiry;
+  dns_address *rhs;
+  uschar *text;
+  int rc;
+  BOOL text_set;
+} dnsbl_cache_block;
+
+
+/* Anchor for DNSBL cache */
+
+static tree_node *dnsbl_cache = NULL;
+
+
+/* Bits for match_type in one_check_dnsbl() */
+
+#define MT_NOT 1
+#define MT_ALL 2
+
+
+/*************************************************
+*          Perform a single dnsbl lookup         *
+*************************************************/
+
+/* 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
+  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)
+  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
+  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
+                 FAIL if not
+*/
+
+static int
+one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
+  uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
+  int defer_return)
+{
+dns_answer * dnsa = store_get_dns_answer();
+dns_scan dnss;
+tree_node *t;
+dnsbl_cache_block *cb;
+int old_pool = store_pool;
+uschar * query;
+int qlen;
+
+/* Construct the specific query domainname */
+
+query = string_sprintf("%s.%s", prepend, domain);
+if ((qlen = Ustrlen(query)) >= 256)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+    "(ignored): %s...", query);
+  return FAIL;
+  }
+
+/* Look for this query in the cache. */
+
+if (  (t = tree_search(dnsbl_cache, query))
+   && (cb = t->data.ptr)->expiry > time(NULL)
+   )
+
+/* Previous lookup was cached */
+
+  {
+  HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
+  }
+
+/* If not cached from a previous lookup, we must do a DNS lookup, and
+cache the result in permanent memory. */
+
+else
+  {
+  uint ttl = 3600;     /* max TTL for positive cache entries */
+
+  store_pool = POOL_PERM;
+
+  if (t)
+    {
+    HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
+    }
+
+  else
+    {  /* Set up a tree entry to cache the lookup */
+    t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
+    Ustrcpy(t->name, query);
+    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
+    (void)tree_insertnode(&dnsbl_cache, t);
+    }
+
+  /* Do the DNS lookup . */
+
+  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 are no longer supported. 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.
+
+  Mark the cache entry with the "now" plus the minimum of the address TTLs,
+  or the RFC 2308 negative-cache value from the SOA if none were found. */
+
+  switch (cb->rc)
+    {
+    case DNS_SUCCEED:
+      {
+      dns_address ** addrp = &cb->rhs;
+      dns_address * da;
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+       if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
+         {
+         *addrp = da;
+         while (da->next) da = da->next;
+         addrp = &da->next;
+         if (ttl > rr->ttl) ttl = rr->ttl;
+         }
+
+      if (cb->rhs)
+       {
+       cb->expiry = time(NULL) + ttl;
+       break;
+       }
+
+      /* 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. */
+
+      cb->rc = DNS_NODATA;
+      }
+      /*FALLTHROUGH*/
+
+    case DNS_NOMATCH:
+    case DNS_NODATA:
+      {
+      /* Although there already is a neg-cache layer maintained by
+      dns_basic_lookup(), we have a dnslist cache entry allocated and
+      tree-inserted. So we may as well use it. */
+
+      time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
+      cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
+      break;
+      }
+
+    default:
+      cb->expiry = time(NULL) + ttl;
+      break;
+    }
+
+  store_pool = old_pool;
+  HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
+    (int)(cb->expiry - time(NULL)));
+  }
+
+/* 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; 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)
+    {
+    for (da = cb->rhs; da; da = da->next)
+      {
+      int ipsep = ',';
+      const uschar *ptr = iplist;
+      uschar *res;
+
+      /* Handle exact matching */
+
+      if (!bitmask)
+       {
+        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
+          if (Ustrcmp(CS da->address, res) == 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(da->address, address) == 1) mask = address[0];
+
+        /* Scan the returned addresses, skipping any that are IPv6 */
+
+        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
+          if (host_aton(res, address) == 1)
+           if ((address[0] & mask) == address[0])
+             break;
+        }
+
+      /* If either
+
+         (a) An IP address in an any ('=') list matched, or
+         (b) No IP address in an all ('==') list matched
+
+      then we're done searching. */
+
+      if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
+      }
+
+    /* If da == NULL, either
+
+       (a) No IP address in an any ('=') list matched, or
+       (b) An IP address in an all ('==') list didn't match
+
+    so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
+    the list. */
+
+    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 %s for %s%c%s\n",
+          res,
+          match_type & MT_ALL ? "=" : "",
+          bitmask ? '&' : '=', iplist);
+        }
+      return FAIL;
+      }
+    }
+
+  /* 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, 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. */
+
+  if (!cb->text_set)
+    {
+    cb->text_set = TRUE;
+    if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+        if (rr->type == T_TXT)
+         {
+         int len = (rr->data)[0];
+         if (len > 511) len = 127;
+         store_pool = POOL_PERM;
+         cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
+         store_pool = old_pool;
+         break;
+         }
+    }
+
+  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      *
+*************************************************/
+
+/* This function runs checks against a list of DNS black lists, until one
+matches. Each item on the list can be of the form
+
+  domain=ip-address/key
+
+The domain is the right-most domain that is used for the query, for example,
+blackholes.mail-abuse.org. If the IP address is present, there is a match only
+if the DNS lookup returns a matching IP address. Several addresses may be
+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:
+
+  dsn.rfc-ignorant.org/$sender_address_domain
+
+After finding a match in the DNS, the domain is placed in $dnslist_domain, and
+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
+
+Arguments:
+  where        the acl type
+  listptr      the domain/address/data list
+  log_msgptr   log message on error
+
+Returns:    OK      successful lookup (i.e. the address is on the list), or
+                      lookup deferred after +include_unknown
+            FAIL    name not found, or no data found for the given type, or
+                      lookup deferred after +exclude_unknown (default)
+            DEFER   lookup failure, if +defer_unknown was set
+*/
+
+int
+verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
+{
+int sep = 0;
+int defer_return = FAIL;
+const uschar *list = *listptr;
+uschar *domain;
+uschar revadd[128];        /* Long enough for IPv6 address */
+
+/* Indicate that the inverted IP address is not yet set up */
+
+revadd[0] = 0;
+
+/* In case this is the first time the DNS resolver is being used. */
+
+dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
+
+/* Loop through all the domains supplied, until something matches */
+
+while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
+  {
+  int rc;
+  BOOL bitmask = FALSE;
+  int match_type = 0;
+  uschar *domain_txt;
+  uschar *comma;
+  uschar *iplist;
+  uschar *key;
+
+  HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
+
+  /* Deal with special values that change the behaviour on defer */
+
+  if (domain[0] == '+')
+    {
+    if      (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
+    else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
+    else if (strcmpic(domain, US"+defer_unknown") == 0)   defer_return = DEFER;
+    else
+      log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
+        domain);
+    continue;
+    }
+
+  /* See if there's explicit data to be looked up */
+
+  if ((key = Ustrchr(domain, '/'))) *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 require all matches
+  and if preceded by ! we invert the result. */
+
+  if (!(iplist = Ustrchr(domain, '=')))
+    {
+    bitmask = TRUE;
+    iplist = Ustrchr(domain, '&');
+    }
+
+  if (iplist)                                 /* Found either = or & */
+    {
+    if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
+      {
+      match_type |= MT_NOT;
+      iplist[-1] = 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
+  looking up TXT records is provided, before the main domain. Otherwise we must
+  set domain_txt == domain. */
+
+  domain_txt = domain;
+  if ((comma = Ustrchr(domain, ',')))
+    {
+    *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
+  actually causing an error here, because that would no doubt hold up incoming
+  mail. Instead, I'll just log it. */
+
+  for (uschar * s = domain; *s; s++)
+    if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
+      {
+      log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
+        "strange characters - is this right?", domain);
+      break;
+      }
+
+  /* Check the alternate domain if present */
+
+  if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
+    if (!isalnum(*s) && *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. */
+
+  if (!key)
+    {
+    if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
+      {
+      *log_msgptr = string_sprintf
+       ("cannot test auto-keyed dnslists condition in %s ACL",
+         acl_wherenames[where]);
+      return ERROR;
+      }
+    if (!sender_host_address) 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, match_type, defer_return);
+    if (rc == OK)
+      {
+      dnslist_domain = string_copy(domain_txt);
+      dnslist_matched = string_copy(sender_host_address);
+      HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
+        sender_host_address, dnslist_domain);
+      }
+    if (rc != FAIL) return rc;     /* OK or DEFER */
+    }
+
+  /* If there is a key string, it can be a list of domains or IP addresses to
+  be concatenated with the main domain. */
+
+  else
+    {
+    int keysep = 0;
+    BOOL defer = FALSE;
+    uschar *keydomain;
+    uschar keyrevadd[128];
+
+    while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
+      {
+      uschar *prepend = keydomain;
+
+      if (string_is_ip_address(keydomain, NULL) != 0)
+        {
+        invert_address(keyrevadd, keydomain);
+        prepend = keyrevadd;
+        }
+
+      rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
+        bitmask, match_type, defer_return);
+      if (rc == OK)
+        {
+        dnslist_domain = string_copy(domain_txt);
+        dnslist_matched = string_copy(keydomain);
+        HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
+          keydomain, dnslist_domain);
+        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. */
+
+      if (rc == DEFER) defer = TRUE;
+      }    /* continue with next keystring domain/address */
+
+    if (defer) return DEFER;
+    }
+  }        /* continue with next dnsdb outer domain */
+
+return FAIL;
+}
+
+/* vi: aw ai sw=2
+*/
+/* End of dnsbl.c.c */
index a0ab1b873875ed1db1c778037dfee0f5eee61419..efc05fcf188ab10889e70263caf678aae0dade56 100644 (file)
@@ -19,27 +19,6 @@ static smtp_context ctctx;
 uschar ctbuffer[8192];
 
 
-/* Structure for caching DNSBL lookups */
-
-typedef struct dnsbl_cache_block {
-  time_t expiry;
-  dns_address *rhs;
-  uschar *text;
-  int rc;
-  BOOL text_set;
-} dnsbl_cache_block;
-
-
-/* Anchor for DNSBL cache */
-
-static tree_node *dnsbl_cache = NULL;
-
-
-/* Bits for match_type in one_check_dnsbl() */
-
-#define MT_NOT 1
-#define MT_ALL 2
-
 static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int);
 
 
@@ -3365,575 +3344,6 @@ same format string, "%s.%s" */
 
 
 
-/*************************************************
-*          Perform a single dnsbl lookup         *
-*************************************************/
-
-/* 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
-  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)
-  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
-  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
-                 FAIL if not
-*/
-
-static int
-one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
-  uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
-  int defer_return)
-{
-dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
-tree_node *t;
-dnsbl_cache_block *cb;
-int old_pool = store_pool;
-uschar * query;
-int qlen;
-
-/* Construct the specific query domainname */
-
-query = string_sprintf("%s.%s", prepend, domain);
-if ((qlen = Ustrlen(query)) >= 256)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
-    "(ignored): %s...", query);
-  return FAIL;
-  }
-
-/* Look for this query in the cache. */
-
-if (  (t = tree_search(dnsbl_cache, query))
-   && (cb = t->data.ptr)->expiry > time(NULL)
-   )
-
-/* Previous lookup was cached */
-
-  {
-  HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
-  }
-
-/* If not cached from a previous lookup, we must do a DNS lookup, and
-cache the result in permanent memory. */
-
-else
-  {
-  uint ttl = 3600;     /* max TTL for positive cache entries */
-
-  store_pool = POOL_PERM;
-
-  if (t)
-    {
-    HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
-    }
-
-  else
-    {  /* Set up a tree entry to cache the lookup */
-    t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
-    Ustrcpy(t->name, query);
-    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
-    (void)tree_insertnode(&dnsbl_cache, t);
-    }
-
-  /* Do the DNS lookup . */
-
-  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 are no longer supported. 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.
-
-  Mark the cache entry with the "now" plus the minimum of the address TTLs,
-  or the RFC 2308 negative-cache value from the SOA if none were found. */
-
-  switch (cb->rc)
-    {
-    case DNS_SUCCEED:
-      {
-      dns_address ** addrp = &cb->rhs;
-      dns_address * da;
-      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
-          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-       if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
-         {
-         *addrp = da;
-         while (da->next) da = da->next;
-         addrp = &da->next;
-         if (ttl > rr->ttl) ttl = rr->ttl;
-         }
-
-      if (cb->rhs)
-       {
-       cb->expiry = time(NULL) + ttl;
-       break;
-       }
-
-      /* 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. */
-
-      cb->rc = DNS_NODATA;
-      }
-      /*FALLTHROUGH*/
-
-    case DNS_NOMATCH:
-    case DNS_NODATA:
-      {
-      /* Although there already is a neg-cache layer maintained by
-      dns_basic_lookup(), we have a dnslist cache entry allocated and
-      tree-inserted. So we may as well use it. */
-
-      time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
-      cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
-      break;
-      }
-
-    default:
-      cb->expiry = time(NULL) + ttl;
-      break;
-    }
-
-  store_pool = old_pool;
-  HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
-    (int)(cb->expiry - time(NULL)));
-  }
-
-/* 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; 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)
-    {
-    for (da = cb->rhs; da; da = da->next)
-      {
-      int ipsep = ',';
-      const uschar *ptr = iplist;
-      uschar *res;
-
-      /* Handle exact matching */
-
-      if (!bitmask)
-       {
-        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
-          if (Ustrcmp(CS da->address, res) == 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(da->address, address) == 1) mask = address[0];
-
-        /* Scan the returned addresses, skipping any that are IPv6 */
-
-        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
-          {
-          if (host_aton(res, address) != 1) continue;
-          if ((address[0] & mask) == address[0]) break;
-          }
-        }
-
-      /* If either
-
-         (a) An IP address in an any ('=') list matched, or
-         (b) No IP address in an all ('==') list matched
-
-      then we're done searching. */
-
-      if (((match_type & MT_ALL) != 0) == (res == NULL)) break;
-      }
-
-    /* If da == NULL, either
-
-       (a) No IP address in an any ('=') list matched, or
-       (b) An IP address in an all ('==') list didn't match
-
-    so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on
-    the list. */
-
-    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 %s for %s%c%s\n",
-          res,
-          ((match_type & MT_ALL) == 0)? "" : "=",
-          bitmask? '&' : '=', iplist);
-        }
-      return FAIL;
-      }
-    }
-
-  /* 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, 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. */
-
-  if (!cb->text_set)
-    {
-    cb->text_set = TRUE;
-    if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
-      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
-           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-        if (rr->type == T_TXT)
-         {
-         int len = (rr->data)[0];
-         if (len > 511) len = 127;
-         store_pool = POOL_PERM;
-         cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
-         store_pool = old_pool;
-         break;
-         }
-    }
-
-  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      *
-*************************************************/
-
-/* This function runs checks against a list of DNS black lists, until one
-matches. Each item on the list can be of the form
-
-  domain=ip-address/key
-
-The domain is the right-most domain that is used for the query, for example,
-blackholes.mail-abuse.org. If the IP address is present, there is a match only
-if the DNS lookup returns a matching IP address. Several addresses may be
-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:
-
-  dsn.rfc-ignorant.org/$sender_address_domain
-
-After finding a match in the DNS, the domain is placed in $dnslist_domain, and
-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
-
-Arguments:
-  where        the acl type
-  listptr      the domain/address/data list
-  log_msgptr   log message on error
-
-Returns:    OK      successful lookup (i.e. the address is on the list), or
-                      lookup deferred after +include_unknown
-            FAIL    name not found, or no data found for the given type, or
-                      lookup deferred after +exclude_unknown (default)
-            DEFER   lookup failure, if +defer_unknown was set
-*/
-
-int
-verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
-{
-int sep = 0;
-int defer_return = FAIL;
-const uschar *list = *listptr;
-uschar *domain;
-uschar revadd[128];        /* Long enough for IPv6 address */
-
-/* Indicate that the inverted IP address is not yet set up */
-
-revadd[0] = 0;
-
-/* In case this is the first time the DNS resolver is being used. */
-
-dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
-
-/* Loop through all the domains supplied, until something matches */
-
-while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
-  {
-  int rc;
-  BOOL bitmask = FALSE;
-  int match_type = 0;
-  uschar *domain_txt;
-  uschar *comma;
-  uschar *iplist;
-  uschar *key;
-
-  HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
-
-  /* Deal with special values that change the behaviour on defer */
-
-  if (domain[0] == '+')
-    {
-    if      (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
-    else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
-    else if (strcmpic(domain, US"+defer_unknown") == 0)   defer_return = DEFER;
-    else
-      log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
-        domain);
-    continue;
-    }
-
-  /* See if there's explicit data to be looked up */
-
-  if ((key = Ustrchr(domain, '/'))) *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 require all matches
-  and if preceded by ! we invert the result. */
-
-  if (!(iplist = Ustrchr(domain, '=')))
-    {
-    bitmask = TRUE;
-    iplist = Ustrchr(domain, '&');
-    }
-
-  if (iplist)                                 /* Found either = or & */
-    {
-    if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
-      {
-      match_type |= MT_NOT;
-      iplist[-1] = 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
-  looking up TXT records is provided, before the main domain. Otherwise we must
-  set domain_txt == domain. */
-
-  domain_txt = domain;
-  if ((comma = Ustrchr(domain, ',')))
-    {
-    *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
-  actually causing an error here, because that would no doubt hold up incoming
-  mail. Instead, I'll just log it. */
-
-  for (uschar * s = domain; *s; s++)
-    if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
-      {
-      log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
-        "strange characters - is this right?", domain);
-      break;
-      }
-
-  /* Check the alternate domain if present */
-
-  if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
-    if (!isalnum(*s) && *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. */
-
-  if (!key)
-    {
-    if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
-      {
-      *log_msgptr = string_sprintf
-       ("cannot test auto-keyed dnslists condition in %s ACL",
-         acl_wherenames[where]);
-      return ERROR;
-      }
-    if (!sender_host_address) 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, match_type, defer_return);
-    if (rc == OK)
-      {
-      dnslist_domain = string_copy(domain_txt);
-      dnslist_matched = string_copy(sender_host_address);
-      HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
-        sender_host_address, dnslist_domain);
-      }
-    if (rc != FAIL) return rc;     /* OK or DEFER */
-    }
-
-  /* If there is a key string, it can be a list of domains or IP addresses to
-  be concatenated with the main domain. */
-
-  else
-    {
-    int keysep = 0;
-    BOOL defer = FALSE;
-    uschar *keydomain;
-    uschar keyrevadd[128];
-
-    while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
-      {
-      uschar *prepend = keydomain;
-
-      if (string_is_ip_address(keydomain, NULL) != 0)
-        {
-        invert_address(keyrevadd, keydomain);
-        prepend = keyrevadd;
-        }
-
-      rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
-        bitmask, match_type, defer_return);
-      if (rc == OK)
-        {
-        dnslist_domain = string_copy(domain_txt);
-        dnslist_matched = string_copy(keydomain);
-        HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
-          keydomain, dnslist_domain);
-        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. */
-
-      if (rc == DEFER) defer = TRUE;
-      }    /* continue with next keystring domain/address */
-
-    if (defer) return DEFER;
-    }
-  }        /* continue with next dnsdb outer domain */
-
-return FAIL;
-}
-
-
-
 /****************************************************
   Verify a local user account for quota sufficiency
 ****************************************************/