added "prvs" and "prvscheck" expansion items
[exim.git] / src / src / dns.c
index 9e66396009adff308e7f8e448a13e1993354957e..f322fafca1679efc8502973db3a9d63a06b651d0 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/dns.c,v 1.4 2005/01/04 10:00:42 ph10 Exp $ */
+/* $Cambridge: exim/src/src/dns.c,v 1.8 2005/06/10 13:38:06 tom Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -153,9 +153,9 @@ else
 *************************************************/
 
 /* Call this with reset == RESET_ANSWERS to scan the answer block, reset ==
-RESET_ADDITIONAL to scan the additional records, and reset == RESET_NEXT to
-get the next record. The result is in static storage which must be copied if
-it is to be preserved.
+RESET_AUTHORITY to scan the authority records, reset == RESET_ADDITIONAL to
+scan the additional records, and reset == RESET_NEXT to get the next record.
+The result is in static storage which must be copied if it is to be preserved.
 
 Arguments:
   dnsa      pointer to dns answer block
@@ -192,12 +192,14 @@ if (reset != RESET_NEXT)
 
   dnss->rrcount = ntohs(h->ancount);
 
-  /* Skip over answers and NS records if wanting to look at the additional
+  /* Skip over answers if we want to look at the authority section. Also skip
+  the NS records (i.e. authority section) if wanting to look at the additional
   records. */
 
-  if (reset == RESET_ADDITIONAL)
+  if (reset == RESET_ADDITIONAL) dnss->rrcount += ntohs(h->nscount);
+
+  if (reset == RESET_AUTHORITY || reset == RESET_ADDITIONAL)
     {
-    dnss->rrcount += ntohs(h->nscount);
     while (dnss->rrcount-- > 0)
       {
       namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
@@ -207,11 +209,11 @@ if (reset != RESET_NEXT)
       GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
       dnss->aptr += dnss->srr.size;         /* skip over it */
       }
-    dnss->rrcount = ntohs(h->arcount);
+    dnss->rrcount = (reset == RESET_AUTHORITY)
+      ? ntohs(h->nscount) : ntohs(h->arcount);
     }
   }
 
-
 /* The variable dnss->aptr is now pointing at the next RR, and dnss->rrcount
 contains the number of RR records left. */
 
@@ -266,7 +268,7 @@ switch(t)
   case T_PTR:   return US"PTR";
   case T_SRV:   return US"SRV";
   case T_NS:    return US"NS";
-  case T_CNAME: return US"CNAME";  
+  case T_CNAME: return US"CNAME";
   default:      return US"?";
   }
 }
@@ -626,8 +628,8 @@ return DNS_FAIL;
 *    Do a DNS lookup and handle virtual types   *
 ************************************************/
 
-/* This function handles some invented "lookup types" that synthesize feature 
-not available in the basic types. The special types all have negative values. 
+/* This function handles some invented "lookup types" that synthesize feature
+not available in the basic types. The special types all have negative values.
 Positive type values are passed straight on to dns_lookup().
 
 Arguments:
@@ -645,7 +647,7 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
-dns_special_lookup(dns_answer *dnsa, uschar *name, int type, 
+dns_special_lookup(dns_answer *dnsa, uschar *name, int type,
   uschar **fully_qualified_name)
 {
 if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
@@ -654,7 +656,7 @@ if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
 
 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 
+/* Find nameservers for the domain or the nearest enclosing zone, excluding the
 root servers. */
 
 if (type == T_ZNS)
@@ -665,10 +667,157 @@ if (type == T_ZNS)
     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; 
+    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)
+  {
+  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 = name;
+    return rc;
     }
-  return DNS_NOMATCH;     
-  } 
+
+  /* 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);
+      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;
+  }
 
 /* Control should never reach here */