Copyright year updates (things touched in 2015)
[exim.git] / src / src / dns.c
index 33520183b22fe90067a946207775aafd12ec9806..f1c038834ee03e216c85788b17bb4e3638b8fe21 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
@@ -53,32 +53,6 @@ Ustrncpy(name, domain, len);
 name[len] = 0;
 endname = name + len;
 
-/* This code, for forcing TRY_AGAIN and NO_RECOVERY, is here so that it works
-for the old test suite that uses a real nameserver. When the old test suite is
-eventually abandoned, this code could be moved into the fakens utility. */
-
-if (len >= 14 && Ustrcmp(endname - 14, "test.again.dns") == 0)
-  {
-  int delay = Uatoi(name);  /* digits at the start of the name */
-  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
-    name, dns_text_type(type));
-  if (delay > 0)
-    {
-    DEBUG(D_dns) debug_printf("delaying %d seconds\n", delay);
-    sleep(delay);
-    }
-  h_errno = TRY_AGAIN;
-  return -1;
-  }
-
-if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0)
-  {
-  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
-    name, dns_text_type(type));
-  h_errno = NO_RECOVERY;
-  return -1;
-  }
-
 /* Look for the fakens utility, and if it exists, call it. */
 
 (void)string_format(utilname, sizeof(utilname), "%s/bin/fakens",
@@ -112,6 +86,13 @@ if (stat(CS utilname, &statbuf) >= 0)
     asize -= rc;      /* may need to be passed on to res_search(). */
     }
 
+  /* If we ran out of output buffer before exhasting the return,
+  carry on reading and counting it. */
+
+  if (asize == 0)
+    while ((rc = read(outfd, name, sizeof(name))) > 0)
+      len += rc;
+
   if (rc < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
       strerror(errno));
@@ -130,7 +111,7 @@ if (stat(CS utilname, &statbuf) >= 0)
   }
 else
   {
-    DEBUG(D_dns) debug_printf("fakens (%s) not found\n", utilname);
+  DEBUG(D_dns) debug_printf("fakens (%s) not found\n", utilname);
   }
 
 /* fakens utility not found, or it returned "pass on" */
@@ -455,6 +436,17 @@ HEADER * h = (HEADER *)dnsa->answer;
 h->ad = 0;
 }
 
+/************************************************
+ *     Check whether the AA bit is set         *
+ *     We need this to warn if we requested AD *
+ *     from a authoritive server               *
+ ************************************************/
+
+BOOL
+dns_is_aa(const dns_answer *dnsa)
+{
+return ((HEADER*)dnsa->answer)->aa;
+}
 
 
 
@@ -789,7 +781,7 @@ for (i = 0; i < 10; i++)
 
   cname_rr.data = type_rr.data = NULL;
   for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
     {
     if (rr->type == type)
@@ -888,25 +880,25 @@ switch (type)
   {
   /* 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);
+    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 */
+    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;
-       }
+    {
+    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
@@ -915,148 +907,147 @@ switch (type)
   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;
-         }
+    {
+    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. */
 
-       /* 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;
 
-       namesuff = Ustrrchr(name, '.');
-       if (namesuff == NULL) return DNS_NOMATCH;
+    /* 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;
-       ipv6 = FALSE;
-       limit = dns_csa_search_limit;
+       limit = 3;
+       }
+      else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
+       {
+       namesuff -= 4;
+       tld = namesuff + 1;
+       ipv6 = TRUE;
+       limit = 3;
+       }
 
-       /* Use more appropriate search parameters if we are in the reverse DNS. */
+    DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
 
-       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;
-               }
-         }
+    /* 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;
+          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;
+      }
 
-       DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
+    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;
+          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+       {
+       if (rr->type != T_SRV) continue;
 
-       /* 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. */
+       /* Extract the numerical SRV fields (p is incremented) */
+       p = rr->data;
+       GETSHORT(priority, p);
+       GETSHORT(weight, p);    weight = weight; /* compiler quietening */
+       GETSHORT(port, p);
 
-       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;
-         }
+       /* Check the CSA version number */
+       if (priority != 1) continue;
 
-       for (i = 0; i < limit; i++)
+       /* If it's making an interesting assertion, return this response. */
+       if (port & 1)
          {
-         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;
-                 }
-               }
+         *fully_qualified_name = namesuff + 1;
+         return DNS_SUCCEED;
          }
-       return DNS_NOMATCH;
        }
+      }
+    return DNS_NOMATCH;
+    }
 
   default:
-       if (type >= 0)
-         return dns_lookup(dnsa, name, type, fully_qualified_name);
+    if (type >= 0)
+      return dns_lookup(dnsa, name, type, fully_qualified_name);
   }
 
 /* Control should never reach here */
@@ -1079,31 +1070,36 @@ Argument:
   dnsa       the DNS answer block
   rr         the RR
 
-Returns:     pointer to a chain of dns_address items
+Returns:     pointer to a chain of dns_address items; NULL when the dnsa was overrun
 */
 
 dns_address *
 dns_address_from_rr(dns_answer *dnsa, dns_record *rr)
 {
-dns_address *yield = NULL;
-
-dnsa = dnsa;    /* Stop picky compilers warning */
+dns_address * yield = NULL;
+uschar * dnsa_lim = dnsa->answer + dnsa->answerlen;
 
 if (rr->type == T_A)
   {
   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;
+  if (p + 4 <= dnsa_lim)
+    {
+    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;
+    }
   }
 
 #if HAVE_IPV6
 
 else
   {
-  yield = store_get(sizeof(dns_address) + 50);
-  inet_ntop(AF_INET6, US rr->data, CS yield->address, 50);
-  yield->next = NULL;
+  if (rr->data + 16 <= dnsa_lim)
+    {
+    yield = store_get(sizeof(dns_address) + 50);
+    inet_ntop(AF_INET6, US rr->data, CS yield->address, 50);
+    yield->next = NULL;
+    }
   }
 #endif  /* HAVE_IPV6 */