Support SOA lookup in dnsdb lookups. Bug 286
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 9 May 2015 18:21:15 +0000 (19:21 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 9 May 2015 21:27:51 +0000 (22:27 +0100)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/dns.c
src/src/lookups/dnsdb.c
test/dnszones-src/db.test.ex
test/scripts/2200-dnsdb/2200
test/src/fakens.c
test/stdout/2200

index 535e81e..80d8aef 100644 (file)
@@ -6878,16 +6878,9 @@ is used on its own as the result. If the lookup does not succeed, the
 &`fail`& keyword causes a &'forced expansion failure'& &-- see section
 &<<SECTforexpfai>>& for an explanation of what this means.
 
-The supported DNS record types are A, CNAME, MX, NS, PTR, SPF, SRV, TLSA and TXT,
-and, when Exim is compiled with IPv6 support, AAAA.
-If no type is given, TXT is assumed. When the type is PTR,
-the data can be an IP address, written as normal; inversion and the addition of
-&%in-addr.arpa%& or &%ip6.arpa%& happens automatically. For example:
-.code
-${lookup dnsdb{ptr=192.168.4.5}{$value}fail}
-.endd
-If the data for a PTR record is not a syntactically valid IP address, it is not
-altered and nothing is added.
+The supported DNS record types are A, CNAME, MX, NS, PTR, SOA, SPF, SRV, TLSA
+and TXT, and, when Exim is compiled with IPv6 support, AAAA.
+If no type is given, TXT is assumed.
 
 For any record type, if multiple records are found, the data is returned as a
 concatenation, with newline as the default separator. The order, of course,
@@ -6899,21 +6892,33 @@ ${lookup dnsdb{>: a=host1.example}}
 .endd
 It is permitted to specify a space as the separator character. Further
 white space is ignored.
+For lookup types that return multiple fields per record,
+an alternate field separator can be specified using a comma after the main
+separator character, followed immediately by the field separator.
+
+.cindex "PTR record" "in &(dnsdb)& lookup"
+When the type is PTR,
+the data can be an IP address, written as normal; inversion and the addition of
+&%in-addr.arpa%& or &%ip6.arpa%& happens automatically. For example:
+.code
+${lookup dnsdb{ptr=192.168.4.5}{$value}fail}
+.endd
+If the data for a PTR record is not a syntactically valid IP address, it is not
+altered and nothing is added.
 
 .cindex "MX record" "in &(dnsdb)& lookup"
 .cindex "SRV record" "in &(dnsdb)& lookup"
 For an MX lookup, both the preference value and the host name are returned for
 each record, separated by a space. For an SRV lookup, the priority, weight,
 port, and host name are returned for each record, separated by spaces.
-An alternate field separator can be specified using a comma after the main
-separator character, followed immediately by the field separator.
+The field separator can be modified as above.
 
 .cindex "TXT record" "in &(dnsdb)& lookup"
 .cindex "SPF record" "in &(dnsdb)& lookup"
 For TXT records with multiple items of data, only the first item is returned,
-unless a separator for them is specified using a comma after the separator
-character followed immediately by the TXT record item separator. To concatenate
-items without a separator, use a semicolon instead. For SPF records the
+unless a field separator is specified.
+To concatenate items without a separator, use a semicolon instead.
+For SPF records the
 default behaviour is to concatenate multiple items without using a separator.
 .code
 ${lookup dnsdb{>\n,: txt=a.b.example}}
@@ -6923,6 +6928,15 @@ ${lookup dnsdb{spf=example.org}}
 It is permitted to specify a space as the separator character. Further
 white space is ignored.
 
+.cindex "SOA record" "in &(dnsdb)& lookup"
+For an SOA lookup, while no result is obtained the lookup is redone with
+successively more leading components dropped from the given domain.
+Only the primary-nameserver field is returned unless a field separator is
+specified.
+.code
+${lookup dnsdb{>:,; soa=a.b.example.com}}
+.endd
+
 .section "Dnsdb lookup modifiers" "SECTdnsdb_mod"
 .cindex "dnsdb modifiers"
 .cindex "modifiers" "dnsdb"
@@ -9722,8 +9736,9 @@ the regular expression from string expansion.
 
 
 .vitem &*${sort{*&<&'string'&>&*}{*&<&'comparator'&>&*}{*&<&'extractor'&>&*}}*&
-.cindex sorting a list
+.cindex sorting "a list"
 .cindex list sorting
+.cindex expansion "list sorting"
 After expansion, <&'string'&> is interpreted as a list, colon-separated by
 default, but the separator can be changed in the usual way.
 The <&'comparator'&> argument is interpreted as the operator
index 89e0ecf..4e6b978 100644 (file)
@@ -94,6 +94,8 @@ HS/02 Bug 1575: exigrep falls back to autodetection of compressed
 
 JH/26 Bug 1539: Add timout/retry options on dnsdb lookups.
 
+JH/27 Bug 286: Support SOA lookup in dnsdb lookups.
+
 
 Exim version 4.85
 -----------------
index a9970d9..ef734d2 100644 (file)
@@ -881,173 +881,179 @@ int
 dns_special_lookup(dns_answer *dnsa, const uschar *name, int type,
   const uschar **fully_qualified_name)
 {
-if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
-
-/* The "mx hosts only" type doesn't require any special action here */
-
-if (type == T_MXH) return dns_lookup(dnsa, name, T_MX, fully_qualified_name);
-
-/* Find nameservers for the domain or the nearest enclosing zone, excluding the
-root servers. */
-
-if (type == T_ZNS)
-  {
-  const uschar *d = name;
-  while (d != 0)
-    {
-    int rc = dns_lookup(dnsa, d, T_NS, fully_qualified_name);
-    if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
-    while (*d != 0 && *d != '.') d++;
-    if (*d++ == 0) break;
-    }
-  return DNS_NOMATCH;
-  }
-
-/* Try to look up the Client SMTP Authorization SRV record for the name. If
-there isn't one, search from the top downwards for a CSA record in a parent
-domain, which might be making assertions about subdomains. If we find a record
-we set fully_qualified_name to whichever lookup succeeded, so that the caller
-can tell whether to look at the explicit authorization field or the subdomain
-assertion field. */
-
-if (type == T_CSA)
+switch (type)
   {
-  uschar *srvname, *namesuff, *tld, *p;
-  int priority, weight, port;
-  int limit, rc, i;
-  BOOL ipv6;
-  dns_record *rr;
-  dns_scan dnss;
-
-  DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);
-
-  srvname = string_sprintf("_client._smtp.%s", name);
-  rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
-  if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
-    {
-    if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name);
-    return rc;
-    }
-
-  /* Search for CSA subdomain assertion SRV records from the top downwards,
-  starting with the 2nd level domain. This order maximizes cache-friendliness.
-  We skip the top level domains to avoid loading their nameservers and because
-  we know they'll never have CSA SRV records. */
-
-  namesuff = Ustrrchr(name, '.');
-  if (namesuff == NULL) return DNS_NOMATCH;
-  tld = namesuff + 1;
-  ipv6 = FALSE;
-  limit = dns_csa_search_limit;
-
-  /* Use more appropriate search parameters if we are in the reverse DNS. */
-
-  if (strcmpic(namesuff, US".arpa") == 0)
-    {
-    if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0)
-      {
-      namesuff -= 8;
-      tld = namesuff + 1;
-      limit = 3;
-      }
-    else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
-      {
-      namesuff -= 4;
-      tld = namesuff + 1;
-      ipv6 = TRUE;
-      limit = 3;
-      }
-    }
-
-  DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
-
-  /* Do not perform the search if the top level or 2nd level domains do not
-  exist. This is quite common, and when it occurs all the search queries would
-  go to the root or TLD name servers, which is not friendly. So we check the
-  AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then
-  the TLD or the 2LD (respectively) doesn't exist and we can skip the search.
-  If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
-  the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */
-
-  if (rc == DNS_NOMATCH)
-    {
-    /* This is really gross. The successful return value from res_search() is
-    the packet length, which is stored in dnsa->answerlen. If we get a
-    negative DNS reply then res_search() returns -1, which causes the bounds
-    checks for name decompression to fail when it is treated as a packet
-    length, which in turn causes the authority search to fail. The correct
-    packet length has been lost inside libresolv, so we have to guess a
-    replacement value. (The only way to fix this properly would be to
-    re-implement res_search() and res_query() so that they don't muddle their
-    success and packet length return values.) For added safety we only reset
-    the packet length if the packet header looks plausible. */
-
-    HEADER *h = (HEADER *)dnsa->answer;
-    if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
-        && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
-        && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
-        && ntohs(h->nscount) >= 1)
-      dnsa->answerlen = MAXPACKET;
-
-    for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
-         rr != NULL;
-         rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-      if (rr->type != T_SOA) continue;
-      else if (strcmpic(rr->name, US"") == 0 ||
-               strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
-      else break;
-    }
-
-  for (i = 0; i < limit; i++)
-    {
-    if (ipv6)
-      {
-      /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP
-      address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */
-      namesuff -= 8;
-      if (namesuff <= name) return DNS_NOMATCH;
-      }
-    else
-      /* Find the start of the preceding domain name label. */
-      do
-        if (--namesuff <= name) return DNS_NOMATCH;
-      while (*namesuff != '.');
-
-    DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);
-
-    srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
-    rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
-    if (rc == DNS_AGAIN) return rc;
-    if (rc != DNS_SUCCEED) continue;
-
-    /* Check that the SRV record we have found is worth returning. We don't
-    just return the first one we find, because some lower level SRV record
-    might make stricter assertions than its parent domain. */
+  /* The "mx hosts only" type doesn't require any special action here */
+  case T_MXH:
+       return dns_lookup(dnsa, name, T_MX, fully_qualified_name);
+
+  /* Find nameservers for the domain or the nearest enclosing zone, excluding
+  the root servers. */
+  case T_ZNS:
+       type = T_NS;
+       /* FALLTHROUGH */
+  case T_SOA:
+       {
+       const uschar *d = name;
+       while (d != 0)
+         {
+         int rc = dns_lookup(dnsa, d, type, fully_qualified_name);
+         if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
+         while (*d != 0 && *d != '.') d++;
+         if (*d++ == 0) break;
+         }
+       return DNS_NOMATCH;
+       }
+
+  /* Try to look up the Client SMTP Authorization SRV record for the name. If
+  there isn't one, search from the top downwards for a CSA record in a parent
+  domain, which might be making assertions about subdomains. If we find a record
+  we set fully_qualified_name to whichever lookup succeeded, so that the caller
+  can tell whether to look at the explicit authorization field or the subdomain
+  assertion field. */
+  case T_CSA:
+       {
+       uschar *srvname, *namesuff, *tld, *p;
+       int priority, weight, port;
+       int limit, rc, i;
+       BOOL ipv6;
+       dns_record *rr;
+       dns_scan dnss;
+
+       DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);
+
+       srvname = string_sprintf("_client._smtp.%s", name);
+       rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
+       if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
+         {
+         if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name);
+         return rc;
+         }
+
+       /* Search for CSA subdomain assertion SRV records from the top downwards,
+       starting with the 2nd level domain. This order maximizes cache-friendliness.
+       We skip the top level domains to avoid loading their nameservers and because
+       we know they'll never have CSA SRV records. */
+
+       namesuff = Ustrrchr(name, '.');
+       if (namesuff == NULL) return DNS_NOMATCH;
+       tld = namesuff + 1;
+       ipv6 = FALSE;
+       limit = dns_csa_search_limit;
+
+       /* Use more appropriate search parameters if we are in the reverse DNS. */
+
+       if (strcmpic(namesuff, US".arpa") == 0)
+         {
+         if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0)
+               {
+               namesuff -= 8;
+               tld = namesuff + 1;
+               limit = 3;
+               }
+         else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
+               {
+               namesuff -= 4;
+               tld = namesuff + 1;
+               ipv6 = TRUE;
+               limit = 3;
+               }
+         }
+
+       DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
+
+       /* Do not perform the search if the top level or 2nd level domains do not
+       exist. This is quite common, and when it occurs all the search queries would
+       go to the root or TLD name servers, which is not friendly. So we check the
+       AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then
+       the TLD or the 2LD (respectively) doesn't exist and we can skip the search.
+       If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
+       the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */
+
+       if (rc == DNS_NOMATCH)
+         {
+         /* This is really gross. The successful return value from res_search() is
+         the packet length, which is stored in dnsa->answerlen. If we get a
+         negative DNS reply then res_search() returns -1, which causes the bounds
+         checks for name decompression to fail when it is treated as a packet
+         length, which in turn causes the authority search to fail. The correct
+         packet length has been lost inside libresolv, so we have to guess a
+         replacement value. (The only way to fix this properly would be to
+         re-implement res_search() and res_query() so that they don't muddle their
+         success and packet length return values.) For added safety we only reset
+         the packet length if the packet header looks plausible. */
+
+         HEADER *h = (HEADER *)dnsa->answer;
+         if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
+                 && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
+                 && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
+                 && ntohs(h->nscount) >= 1)
+               dnsa->answerlen = MAXPACKET;
+
+         for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
+                  rr != NULL;
+                  rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+               if (rr->type != T_SOA) continue;
+               else if (strcmpic(rr->name, US"") == 0 ||
+                                strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
+               else break;
+         }
+
+       for (i = 0; i < limit; i++)
+         {
+         if (ipv6)
+               {
+               /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP
+               address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */
+               namesuff -= 8;
+               if (namesuff <= name) return DNS_NOMATCH;
+               }
+         else
+               /* Find the start of the preceding domain name label. */
+               do
+                 if (--namesuff <= name) return DNS_NOMATCH;
+               while (*namesuff != '.');
+
+         DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);
+
+         srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
+         rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
+         if (rc == DNS_AGAIN) return rc;
+         if (rc != DNS_SUCCEED) continue;
+
+         /* Check that the SRV record we have found is worth returning. We don't
+         just return the first one we find, because some lower level SRV record
+         might make stricter assertions than its parent domain. */
+
+         for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+                  rr != NULL;
+                  rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+               {
+               if (rr->type != T_SRV) continue;
+
+               /* Extract the numerical SRV fields (p is incremented) */
+               p = rr->data;
+               GETSHORT(priority, p);
+               GETSHORT(weight, p);    weight = weight; /* compiler quietening */
+               GETSHORT(port, p);
+
+               /* Check the CSA version number */
+               if (priority != 1) continue;
+
+               /* If it's making an interesting assertion, return this response. */
+               if (port & 1)
+                 {
+                 *fully_qualified_name = namesuff + 1;
+                 return DNS_SUCCEED;
+                 }
+               }
+         }
+       return DNS_NOMATCH;
+       }
 
-    for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-         rr != NULL;
-         rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-      {
-      if (rr->type != T_SRV) continue;
-
-      /* Extract the numerical SRV fields (p is incremented) */
-      p = rr->data;
-      GETSHORT(priority, p);
-      GETSHORT(weight, p);     weight = weight; /* compiler quietening */
-      GETSHORT(port, p);
-
-      /* Check the CSA version number */
-      if (priority != 1) continue;
-
-      /* If it's making an interesting assertion, return this response. */
-      if (port & 1)
-        {
-        *fully_qualified_name = namesuff + 1;
-        return DNS_SUCCEED;
-        }
-      }
-    }
-  return DNS_NOMATCH;
+  default:
+       if (type >= 0)
+         return dns_lookup(dnsa, name, type, fully_qualified_name);
   }
 
 /* Control should never reach here */
@@ -1064,16 +1070,13 @@ return DNS_FAIL;
 *************************************************/
 
 /* The record type is either T_A for an IPv4 address or T_AAAA (or T_A6 when
-supported) for an IPv6 address. In the A6 case, there may be several addresses,
-generated by following chains. A recursive function does all the hard work. A6
-records now look like passing into history, so the code is only included when
-explicitly asked for.
+supported) for an IPv6 address.
 
 Argument:
   dnsa       the DNS answer block
   rr         the RR
 
-Returns:     pointer a chain of dns_address items
+Returns:     pointer to a chain of dns_address items
 */
 
 dns_address *
@@ -1085,7 +1088,7 @@ dnsa = dnsa;    /* Stop picky compilers warning */
 
 if (rr->type == T_A)
   {
-  uschar *p = (uschar *)(rr->data);
+  uschar *p = US rr->data;
   yield = store_get(sizeof(dns_address) + 20);
   (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
   yield->next = NULL;
@@ -1096,7 +1099,7 @@ if (rr->type == T_A)
 else
   {
   yield = store_get(sizeof(dns_address) + 50);
-  inet_ntop(AF_INET6, (uschar *)(rr->data), CS yield->address, 50);
+  inet_ntop(AF_INET6, US rr->data, CS yield->address, 50);
   yield->next = NULL;
   }
 #endif  /* HAVE_IPV6 */
index a0a457a..d06455e 100644 (file)
@@ -41,6 +41,7 @@ static const char *type_names[] = {
   "mxh",
   "ns",
   "ptr",
+  "soa",
   "spf",
   "srv",
   "tlsa",
@@ -60,6 +61,7 @@ static int type_values[] = {
   T_MXH,     /* Private type for "MX hostnames" */
   T_NS,
   T_PTR,
+  T_SOA,
   T_SPF,
   T_SRV,
   T_TLSA,
@@ -386,10 +388,6 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
       {
       if (rr->type != searchtype) continue;
 
-      /* There may be several addresses from an A6 record. Put the configured
-      separator between them, just as for between several records. However, A6
-      support is not normally configured these days. */
-
       if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
         {
         dns_address *da;
@@ -436,7 +434,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
         uint16_t i, payload_length;
         uschar s[MAX_TLSA_EXPANDED_SIZE];
        uschar * sp = s;
-        uschar *p = (uschar *)(rr->data);
+        uschar * p = US rr->data;
 
         usage = *p++;
         selector = *p++;
@@ -449,72 +447,75 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
         for (i=0;
              i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4);
              i++)
-          {
           sp += sprintf(CS sp, "%02x", (unsigned char)p[i]);
-          }
+
         yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
         }
-      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */
+      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
         {
         int priority, weight, port;
         uschar s[264];
-        uschar *p = (uschar *)(rr->data);
-
-        if (type == T_MXH)
-          {
-          /* mxh ignores the priority number and includes only the hostnames */
-          GETSHORT(priority, p);
-          }
-        else if (type == T_MX)
-          {
-          GETSHORT(priority, p);
-          sprintf(CS s, "%d%c", priority, *outsep2);
-          yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
-          }
-        else if (type == T_SRV)
-          {
-          GETSHORT(priority, p);
-          GETSHORT(weight, p);
-          GETSHORT(port, p);
-          sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
-                                       weight, *outsep2, port, *outsep2);
-          yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
-          }
-        else if (type == T_CSA)
-          {
-          /* See acl_verify_csa() for more comments about CSA. */
-
-          GETSHORT(priority, p);
-          GETSHORT(weight, p);
-          GETSHORT(port, p);
-
-          if (priority != 1) continue;      /* CSA version must be 1 */
-
-          /* If the CSA record we found is not the one we asked for, analyse
-          the subdomain assertions in the port field, else analyse the direct
-          authorization status in the weight field. */
-
-          if (Ustrcmp(found, domain) != 0)
-            {
-            if (port & 1) *s = 'X';         /* explicit authorization required */
-            else *s = '?';                  /* no subdomain assertions here */
-            }
-          else
-            {
-            if (weight < 2) *s = 'N';       /* not authorized */
-            else if (weight == 2) *s = 'Y'; /* authorized */
-            else if (weight == 3) *s = '?'; /* unauthorizable */
-            else continue;                  /* invalid */
-            }
-
-          s[1] = ' ';
-          yield = string_cat(yield, &size, &ptr, s, 2);
-          }
+        uschar * p = US rr->data;
+
+       switch (type)
+         {
+         case T_MXH:
+           /* mxh ignores the priority number and includes only the hostnames */
+           GETSHORT(priority, p);
+           break;
+
+         case T_MX:
+           GETSHORT(priority, p);
+           sprintf(CS s, "%d%c", priority, *outsep2);
+           yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+           break;
+
+         case T_SRV:
+           GETSHORT(priority, p);
+           GETSHORT(weight, p);
+           GETSHORT(port, p);
+           sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
+                             weight, *outsep2, port, *outsep2);
+           yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+           break;
+
+         case T_CSA:
+           /* See acl_verify_csa() for more comments about CSA. */
+           GETSHORT(priority, p);
+           GETSHORT(weight, p);
+           GETSHORT(port, p);
+
+           if (priority != 1) continue;      /* CSA version must be 1 */
+
+           /* If the CSA record we found is not the one we asked for, analyse
+           the subdomain assertions in the port field, else analyse the direct
+           authorization status in the weight field. */
+
+           if (Ustrcmp(found, domain) != 0)
+             {
+             if (port & 1) *s = 'X';         /* explicit authorization required */
+             else *s = '?';                  /* no subdomain assertions here */
+             }
+           else
+             {
+             if (weight < 2) *s = 'N';       /* not authorized */
+             else if (weight == 2) *s = 'Y'; /* authorized */
+             else if (weight == 3) *s = '?'; /* unauthorizable */
+             else continue;                  /* invalid */
+             }
+
+           s[1] = ' ';
+           yield = string_cat(yield, &size, &ptr, s, 2);
+           break;
+
+         default:
+           break;
+         }
 
         /* GETSHORT() has advanced the pointer to the target domain. */
 
         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
-          (DN_EXPAND_ARG4_TYPE)(s), sizeof(s));
+          (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
 
         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
@@ -526,6 +527,32 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
           break;
           }
         else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+
+       if (type == T_SOA && outsep2 != NULL)
+         {
+         unsigned long serial, refresh, retry, expire, minimum;
+
+         p += rc;
+         yield = string_cat(yield, &size, &ptr, outsep2, 1);
+
+         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+           (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
+         if (rc < 0)
+           {
+           log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
+             "domain=%s", dns_text_type(type), domain);
+           break;
+           }
+         else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+
+         p += rc;
+         GETLONG(serial, p); GETLONG(refresh, p);
+         GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
+         sprintf(CS s, "%c%d%c%d%c%d%c%d%c%d",
+           *outsep2, serial, *outsep2, refresh,
+           *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
+         yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+         }
         }
       }    /* Loop for list of returned records */
 
index 1339981..c9b004c 100644 (file)
@@ -1,5 +1,5 @@
 ; This is a testing zone file for use when testing DNS handling in Exim. This
-; is a fake zone of no real use - hence no SOA record. The zone name is
+; is a fake zone of no real use. The zone name is
 ; test.ex. This file is passed through the substitution mechanism before being
 ; used by the fakens auxiliary program. This inserts the actual IP addresses
 ; of the local host into the zone.
@@ -17,6 +17,7 @@
 ; host ever uses them.
 
 test.ex.     NS      exim.test.ex.
+test.ex.     SOA     exim.test.ex. hostmaster.exim.test.ex 1430683638 1200 120 604800 3600
 
 test.ex.     TXT     "A TXT record for test.ex."
 s/lash       TXT     "A TXT record for s/lash.test.ex."
index 837c85a..40837db 100644 (file)
@@ -21,6 +21,9 @@ srv=_smtp._tcp.nosmtp.test.ex    ${lookup dnsdb{srv=_smtp._tcp.nosmtp.test.ex}{$
 csa=csa1.test.ex           ${lookup dnsdb{csa=csa1.test.ex}}
 csa=csa2.test.ex           ${lookup dnsdb{csa=csa2.test.ex}}
 
+soa=test.ex                ${lookup dnsdb{soa=test.ex}{$value}{fail}}
+soa=a.test.ex              ${lookup dnsdb{>:, soa=test.ex}{$value}{fail}}
+
 # DNS lookups with multiple items
 
 ten-1:ten2                 ${lookup dnsdb{a=ten-1.test.ex:ten-2.test.ex}}
index 31b6471..fc7848e 100644 (file)
@@ -130,7 +130,7 @@ static tlist type_list[] = {
   { US"A",       ns_t_a },
   { US"NS",      ns_t_ns },
   { US"CNAME",   ns_t_cname },
-/*  { US"SOA",     ns_t_soa },  Not currently in use */
+  { US"SOA",     ns_t_soa },
   { US"PTR",     ns_t_ptr },
   { US"MX",      ns_t_mx },
   { US"TXT",     ns_t_txt },
@@ -227,6 +227,22 @@ while (isspace(*p)) p++;
 return pk;
 }
 
+uschar *
+longfield(uschar ** pp, uschar * pk)
+{
+unsigned long value = 0;
+uschar * p = *pp;
+
+while (isdigit(*p)) value = value*10 + *p++ - '0';
+while (isspace(*p)) p++;
+*pp = p;
+*pk++ = (value >> 24) & 255;
+*pk++ = (value >> 16) & 255;
+*pk++ = (value >> 8) & 255;
+*pk++ = value & 255;
+return pk;
+}
+
 
 
 /*************************************************/
@@ -316,7 +332,7 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL)
   uschar *rdlptr;
   uschar *p, *ep, *pp;
   BOOL found_cname = FALSE;
-  int i, plen, value;
+  int i, value;
   int tvalue = typeptr->value;
   int qtlen = qtypelen;
   BOOL rr_sec = FALSE;
@@ -436,60 +452,72 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL)
 
   switch (tvalue)
     {
-    case ns_t_soa:  /* Not currently used */
-    break;
+    case ns_t_soa:
+      p = strtok(p, " ");
+      ep = p + strlen(p);
+      if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
+      pk = packname(p, pk);                    /* primary ns */
+      p = strtok(NULL, " ");
+      pk = packname(p , pk);                   /* responsible mailbox */
+      *(p += strlen(p)) = ' ';
+      while (isspace(*p)) p++;
+      pk = longfield(&p, pk);                  /* serial */
+      pk = longfield(&p, pk);                  /* refresh */
+      pk = longfield(&p, pk);                  /* retry */
+      pk = longfield(&p, pk);                  /* expire */
+      pk = longfield(&p, pk);                  /* minimum */
+      break;
 
     case ns_t_a:
-    for (i = 0; i < 4; i++)
-      {
-      value = 0;
-      while (isdigit(*p)) value = value*10 + *p++ - '0';
-      *pk++ = value;
-      p++;
-      }
-    break;
+      for (i = 0; i < 4; i++)
+       {
+       value = 0;
+       while (isdigit(*p)) value = value*10 + *p++ - '0';
+       *pk++ = value;
+       p++;
+       }
+      break;
 
     /* The only occurrence of a double colon is for ::1 */
     case ns_t_aaaa:
-    if (Ustrcmp(p, "::1") == 0)
-      {
-      memset(pk, 0, 15);
-      pk += 15;
-      *pk++ = 1;
-      }
-    else for (i = 0; i < 8; i++)
-      {
-      value = 0;
-      while (isxdigit(*p))
-        {
-        value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7');
-        p++;
-        }
-      *pk++ = (value >> 8) & 255;
-      *pk++ = value & 255;
-      p++;
-      }
-    break;
+      if (Ustrcmp(p, "::1") == 0)
+       {
+       memset(pk, 0, 15);
+       pk += 15;
+       *pk++ = 1;
+       }
+      else for (i = 0; i < 8; i++)
+       {
+       value = 0;
+       while (isxdigit(*p))
+         {
+         value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7');
+         p++;
+         }
+       *pk++ = (value >> 8) & 255;
+       *pk++ = value & 255;
+       p++;
+       }
+      break;
 
     case ns_t_mx:
-    pk = shortfield(&p, pk);
-    if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
-    pk = packname(p, pk);
-    plen = Ustrlen(p);
-    break;
+      pk = shortfield(&p, pk);
+      if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
+      pk = packname(p, pk);
+      break;
 
     case ns_t_txt:
-    pp = pk++;
-    if (*p == '"') p++;   /* Should always be the case */
-    while (*p != 0 && *p != '"') *pk++ = *p++;
-    *pp = pk - pp - 1;
-    break;
+      pp = pk++;
+      if (*p == '"') p++;   /* Should always be the case */
+      while (*p != 0 && *p != '"') *pk++ = *p++;
+      *pp = pk - pp - 1;
+      break;
 
     case ns_t_tlsa:
-    pk = bytefield(&p, pk);    /* usage */
-    pk = bytefield(&p, pk);    /* selector */
-    pk = bytefield(&p, pk);    /* match type */
-    while (isxdigit(*p))
+      pk = bytefield(&p, pk);  /* usage */
+      pk = bytefield(&p, pk);  /* selector */
+      pk = bytefield(&p, pk);  /* match type */
+      while (isxdigit(*p))
       {
       value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4;
       if (isxdigit(*++p))
@@ -500,27 +528,26 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL)
       *pk++ = value & 255;
       }
 
-    break;
+      break;
 
     case ns_t_srv:
-    for (i = 0; i < 3; i++)
-      {
-      value = 0;
-      while (isdigit(*p)) value = value*10 + *p++ - '0';
-      while (isspace(*p)) p++;
-      *pk++ = (value >> 8) & 255;
-      *pk++ = value & 255;
-      }
+      for (i = 0; i < 3; i++)
+       {
+       value = 0;
+       while (isdigit(*p)) value = value*10 + *p++ - '0';
+       while (isspace(*p)) p++;
+       *pk++ = (value >> 8) & 255;
+       *pk++ = value & 255;
+       }
 
     /* Fall through */
 
     case ns_t_cname:
     case ns_t_ns:
     case ns_t_ptr:
-    if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
-    pk = packname(p, pk);
-    plen = Ustrlen(p);
-    break;
+      if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
+      pk = packname(p, pk);
+      break;
     }
 
   /* Fill in the length, and we are done with this RR */
index 3d7d0db..b775948 100644 (file)
@@ -19,6 +19,9 @@
 > csa=csa1.test.ex           Y csa1.test.ex
 > csa=csa2.test.ex           N csa2.test.ex
 > 
+> soa=test.ex                exim.test.ex
+> soa=a.test.ex              exim.test.ex hostmaster.exim.test.ex 1430683638 1200 120 604800 3600
+> 
 > # DNS lookups with multiple items
 > 
 > ten-1:ten2                 V4NET.0.0.1