Fix recursive inclusion of spf.h
[exim.git] / src / src / dns.c
index 237b734a6ba42acea2891fc7dda70d83b82f4c2f..f322fafca1679efc8502973db3a9d63a06b651d0 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/dns.c,v 1.1 2004/10/07 10:39:01 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    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
@@ -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. */
 
@@ -258,14 +260,16 @@ dns_text_type(int t)
 {
 switch(t)
   {
-  case T_A:    return US"A";
-  case T_MX:   return US"MX";
-  case T_AAAA: return US"AAAA";
-  case T_A6:   return US"A6";
-  case T_TXT:  return US"TXT";
-  case T_PTR:  return US"PTR";
-  case T_SRV:  return US"SRV";
-  default:     return US"?";
+  case T_A:     return US"A";
+  case T_MX:    return US"MX";
+  case T_AAAA:  return US"AAAA";
+  case T_A6:    return US"A6";
+  case T_TXT:   return US"TXT";
+  case T_PTR:   return US"PTR";
+  case T_SRV:   return US"SRV";
+  case T_NS:    return US"NS";
+  case T_CNAME: return US"CNAME";
+  default:      return US"?";
   }
 }
 
@@ -617,6 +621,211 @@ 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.
+Positive type values are passed straight on to dns_lookup().
+
+Arguments:
+  dnsa                  pointer to dns_answer structure
+  name                  domain name to look up
+  type                  DNS record type (T_A, T_MX, etc or a "special")
+  fully_qualified_name  if not NULL, return the returned name here if its
+                          contents are different (i.e. it must be preset)
+
+Returns:                DNS_SUCCEED   successful lookup
+                        DNS_NOMATCH   name not found
+                        DNS_NODATA    no data found
+                        DNS_AGAIN     soft failure, try again later
+                        DNS_FAIL      DNS failure
+*/
+
+int
+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);
+
+/* 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)
+  {
+  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)
+  {
+  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;
+    }
+
+  /* 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 */
+
+return DNS_FAIL;
+}
+
+
+
 /* Support for A6 records has been commented out since they were demoted to
 experimental status at IETF 51. */