Allow a list of IP addresses or names to follow / in a dnslists ACL
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Mon, 22 Nov 2004 11:30:03 +0000 (11:30 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Mon, 22 Nov 2004 11:30:03 +0000 (11:30 +0000)
condition.

doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/verify.c

index 1def4136430d2b87024c8a8b14dd1b332579d2ba..1d294c8574c6f31449b8ef59b130ed495ba4767b 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.35 2004/11/19 15:18:57 ph10 Exp $
+$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.36 2004/11/22 11:30:03 ph10 Exp $
 
 Change log file for Exim from version 4.21
 -------------------------------------------
 
 Change log file for Exim from version 4.21
 -------------------------------------------
@@ -160,6 +160,10 @@ Exim version 4.44
     (3) It is now possible to specify the separator character for use when
         multiple records are returned.
 
     (3) It is now possible to specify the separator character for use when
         multiple records are returned.
 
+38. The dnslists ACL condition has been extended: it is now possible to supply
+    a list of IP addresses and/or domains to be looked up in a particular DNS
+    domain.
+
 
 Exim version 4.43
 -----------------
 
 Exim version 4.43
 -----------------
index 810607a5bb545c60b26f8a61920bcd8bde221fbb..be1bbfe16fb9299760a35d5784e9fb52cb941815 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/NewStuff,v 1.13 2004/11/19 15:18:57 ph10 Exp $
+$Cambridge: exim/doc/doc-txt/NewStuff,v 1.14 2004/11/22 11:30:03 ph10 Exp $
 
 New Features in Exim
 --------------------
 
 New Features in Exim
 --------------------
@@ -166,6 +166,36 @@ Version 4.44
     The same effect could be achieved by wrapping the lookup in ${tr...}; this
     feature is just a syntactic simplification.
 
     The same effect could be achieved by wrapping the lookup in ${tr...}; this
     feature is just a syntactic simplification.
 
+15. It is now possible to supply a list of domains and/or IP addresses to be
+    lookup up in a DNS blacklist. Previously, only a single domain name could
+    be given, for example:
+
+      dnslists = black.list.tld/$sender_host_name
+
+    What follows the slash can now be a list. As with all lists, the default
+    separator is a colon. However, because this is a sublist within the list of
+    DNS blacklist domains, it is necessary either to double the separators like
+    this:
+
+      dnslists = black.list.tld/name.1::name.2
+
+    or to change the separator character, like this:
+
+      dnslists = black.list.tld/<;name.1;name.2
+
+    If an item in the list is an IP address, it is inverted before the DNS
+    blacklist domain is appended. If it is not an IP address, no inversion
+    occurs. Consider this condition:
+
+       dnslists = black.list.tls/<;192.168.1.2;a.domain
+
+    The DNS lookups that occur are for
+
+       2.1.168.192.black.list.tld  and  a.domain.black.list.tld
+
+    Once a DNS record has been found (that matches a specific IP return
+    address, if specified), no further lookups are done.
+
 
 Version 4.43
 ------------
 
 Version 4.43
 ------------
index cd2ae666c8f15b4661ed92a89c1d9f7b197bb65c..0c1d6b0ebe3b0b56c987ce93f82db02e41affa3f 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.6 2004/11/18 11:17:33 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.7 2004/11/22 11:30:04 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -2183,6 +2183,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      *
 *************************************************/
 /*************************************************
 *        Check host against DNS black lists      *
 *************************************************/
@@ -2227,7 +2475,6 @@ verify_check_dnsbl(uschar **listptr)
 {
 int sep = 0;
 int defer_return = FAIL;
 {
 int sep = 0;
 int defer_return = FAIL;
-int old_pool = store_pool;
 BOOL invert_result = FALSE;
 uschar *list = *listptr;
 uschar *domain;
 BOOL invert_result = FALSE;
 uschar *list = *listptr;
 uschar *domain;
@@ -2240,18 +2487,19 @@ uschar revadd[128];        /* Long enough for IPv6 address */
 
 revadd[0] = 0;
 
 
 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)
   {
 /* 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;
   BOOL frc;
   BOOL bitmask = FALSE;
-  dns_answer dnsa;
-  dns_scan dnss;
   uschar *iplist;
   uschar *key;
   uschar *iplist;
   uschar *key;
-  tree_node *t;
-  dnsbl_cache_block *cb;
 
   HDEBUG(D_dnsbl) debug_printf("DNS list check: %s\n", domain);
 
 
   HDEBUG(D_dnsbl) debug_printf("DNS list check: %s\n", domain);
 
@@ -2310,251 +2558,80 @@ 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);
   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
     {
   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;
+    uschar *keydomain; 
+    uschar keybuffer[256];
+  
+    while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, 
+            sizeof(keybuffer))) != NULL)
+      {       
+      if (string_is_ip_address(keydomain, 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;
+        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);
         }
         }
-      }
+         
+      if (rc != FAIL) return rc;   /* OK or DEFER */
 
 
-    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;
-    }
-
-  /* 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",
-      (key == NULL)? sender_host_address : key, domain);
-    }
-  }      /* Continue with next domain */
+      }    /* continue with next keystring domain/address */
+    }  
+  }        /* continue with next dnsdb outer domain */
 
 return FAIL;
 }
 
 return FAIL;
 }