Copyright year updates:
[exim.git] / src / src / dns.c
index f322fafca1679efc8502973db3a9d63a06b651d0..88fa36baa2eaddd56a4f58ccb528a0030cf57e08 100644 (file)
@@ -1,10 +1,8 @@
-/* $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 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2013 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
@@ -23,6 +21,133 @@ static void dns_complete_a6(dns_address ***, dns_answer *, dns_record *,
 #endif
 
 
+/*************************************************
+*               Fake DNS resolver                *
+*************************************************/
+
+/* This function is called instead of res_search() when Exim is running in its
+test harness. It recognizes some special domain names, and uses them to force
+failure and retry responses (optionally with a delay). Otherwise, it calls an
+external utility that mocks-up a nameserver, if it can find the utility.
+If not, it passes its arguments on to res_search(). The fake nameserver may
+also return a code specifying that the name should be passed on.
+
+Background: the original test suite required a real nameserver to carry the
+test zones, whereas the new test suit has the fake server for portability. This
+code supports both.
+
+Arguments:
+  domain      the domain name
+  type        the DNS record type
+  answerptr   where to put the answer
+  size        size of the answer area
+
+Returns:      length of returned data, or -1 on error (h_errno set)
+*/
+
+static int
+fakens_search(uschar *domain, int type, uschar *answerptr, int size)
+{
+int len = Ustrlen(domain);
+int asize = size;                  /* Locally modified */
+uschar *endname;
+uschar name[256];
+uschar utilname[256];
+uschar *aptr = answerptr;          /* Locally modified */
+struct stat statbuf;
+
+/* Remove terminating dot. */
+
+if (domain[len - 1] == '.') len--;
+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",
+  spool_directory);
+
+if (stat(CS utilname, &statbuf) >= 0)
+  {
+  pid_t pid;
+  int infd, outfd, rc;
+  uschar *argv[5];
+
+  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n",
+    name, dns_text_type(type));
+
+  argv[0] = utilname;
+  argv[1] = spool_directory;
+  argv[2] = name;
+  argv[3] = dns_text_type(type);
+  argv[4] = NULL;
+
+  pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE);
+  if (pid < 0)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to run fakens: %s",
+      strerror(errno));
+
+  len = 0;
+  rc = -1;
+  while (asize > 0 && (rc = read(outfd, aptr, asize)) > 0)
+    {
+    len += rc;
+    aptr += rc;       /* Don't modify the actual arguments, because they */
+    asize -= rc;      /* may need to be passed on to res_search(). */
+    }
+
+  if (rc < 0)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
+      strerror(errno));
+
+  switch(child_close(pid, 0))
+    {
+    case 0: return len;
+    case 1: h_errno = HOST_NOT_FOUND; return -1;
+    case 2: h_errno = TRY_AGAIN; return -1;
+    default:
+    case 3: h_errno = NO_RECOVERY; return -1;
+    case 4: h_errno = NO_DATA; return -1;
+    case 5: /* Pass on to res_search() */
+    DEBUG(D_dns) debug_printf("fakens returned PASS_ON\n");
+    }
+  }
+
+/* fakens utility not found, or it returned "pass on" */
+
+DEBUG(D_dns) debug_printf("passing %s on to res_search()\n", domain);
+
+return res_search(CS domain, C_IN, type, answerptr, size);
+}
+
+
 
 /*************************************************
 *        Initialize and configure resolver       *
@@ -41,18 +166,72 @@ Returns:            nothing
 void
 dns_init(BOOL qualify_single, BOOL search_parents)
 {
-if ((_res.options & RES_INIT) == 0)
+res_state resp = os_get_dns_resolver_res();
+
+if ((resp->options & RES_INIT) == 0)
   {
-  DEBUG(D_resolver) _res.options |= RES_DEBUG;     /* For Cygwin */
+  DEBUG(D_resolver) resp->options |= RES_DEBUG;     /* For Cygwin */
+  os_put_dns_resolver_res(resp);
   res_init();
-  DEBUG(D_resolver) _res.options |= RES_DEBUG;
+  DEBUG(D_resolver) resp->options |= RES_DEBUG;
+  os_put_dns_resolver_res(resp);
   }
 
-_res.options &= ~(RES_DNSRCH | RES_DEFNAMES);
-_res.options |= (qualify_single? RES_DEFNAMES : 0) |
+resp->options &= ~(RES_DNSRCH | RES_DEFNAMES);
+resp->options |= (qualify_single? RES_DEFNAMES : 0) |
                 (search_parents? RES_DNSRCH : 0);
-if (dns_retrans > 0) _res.retrans = dns_retrans;
-if (dns_retry > 0) _res.retry = dns_retry;
+if (dns_retrans > 0) resp->retrans = dns_retrans;
+if (dns_retry > 0) resp->retry = dns_retry;
+
+#ifdef RES_USE_EDNS0
+if (dns_use_edns0 >= 0)
+  {
+  if (dns_use_edns0)
+    resp->options |= RES_USE_EDNS0;
+  else
+    resp->options &= ~RES_USE_EDNS0;
+  DEBUG(D_resolver)
+    debug_printf("Coerced resolver EDNS0 support %s.\n",
+        dns_use_edns0 ? "on" : "off");
+  }
+#else
+if (dns_use_edns0 >= 0)
+  DEBUG(D_resolver)
+    debug_printf("Unable to %sset EDNS0 without resolver support.\n",
+        dns_use_edns0 ? "" : "un");
+#endif
+
+#ifndef DISABLE_DNSSEC
+# ifdef RES_USE_DNSSEC
+#  ifndef RES_USE_EDNS0
+#   error Have RES_USE_DNSSEC but not RES_USE_EDNS0?  Something hinky ...
+#  endif
+if (dns_dnssec_ok >= 0)
+  {
+  if (dns_use_edns0 == 0 && dns_dnssec_ok != 0)
+    {
+    DEBUG(D_resolver)
+      debug_printf("CONFLICT: dns_use_edns0 forced false, dns_dnssec_ok forced true, ignoring latter!\n");
+    }
+  else
+    {
+    if (dns_dnssec_ok)
+      resp->options |= RES_USE_DNSSEC;
+    else
+      resp->options &= ~RES_USE_DNSSEC;
+    DEBUG(D_resolver) debug_printf("Coerced resolver DNSSEC support %s.\n",
+        dns_dnssec_ok ? "on" : "off");
+    }
+  }
+# else
+if (dns_dnssec_ok >= 0)
+  DEBUG(D_resolver)
+    debug_printf("Unable to %sset DNSSEC without resolver support.\n",
+        dns_dnssec_ok ? "" : "un");
+# endif
+#endif /* DISABLE_DNSSEC */
+
+os_put_dns_resolver_res(resp);
 }
 
 
@@ -245,11 +424,40 @@ return &(dnss->srr);
 
 
 
+/*************************************************
+*    Return whether AD bit set in DNS result     *
+*************************************************/
+
+/* We do not perform DNSSEC work ourselves; if the administrator has installed
+a verifying resolver which sets AD as appropriate, though, we'll use that.
+(AD = Authentic Data)
+
+Argument:   pointer to dns answer block
+Returns:    bool indicating presence of AD bit
+*/
+
+BOOL
+dns_is_secure(dns_answer *dnsa)
+{
+#ifdef DISABLE_DNSSEC
+DEBUG(D_dns)
+  debug_printf("DNSSEC support disabled at build-time; dns_is_secure() false\n");
+return FALSE;
+#else
+HEADER *h = (HEADER *)dnsa->answer;
+return h->ad ? TRUE : FALSE;
+#endif
+}
+
+
+
+
 /*************************************************
 *            Turn DNS type into text             *
 *************************************************/
 
-/* Turn the coded record type into a string for printing.
+/* Turn the coded record type into a string for printing. All those that Exim
+uses should be included here.
 
 Argument:   record type
 Returns:    pointer to string
@@ -265,7 +473,9 @@ switch(t)
   case T_AAAA:  return US"AAAA";
   case T_A6:    return US"A6";
   case T_TXT:   return US"TXT";
+  case T_SPF:   return US"SPF";
   case T_PTR:   return US"PTR";
+  case T_SOA:   return US"SOA";
   case T_SRV:   return US"SRV";
   case T_NS:    return US"NS";
   case T_CNAME: return US"CNAME";
@@ -295,9 +505,10 @@ Returns:     the return code
 static int
 dns_return(uschar *name, int type, int rc)
 {
+res_state resp = os_get_dns_resolver_res();
 tree_node *node = store_get_perm(sizeof(tree_node) + 290);
 sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
-  _res.options);
+  resp->options);
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
 return rc;
@@ -324,6 +535,7 @@ Arguments:
 Returns:    DNS_SUCCEED   successful lookup
             DNS_NOMATCH   name not found (NXDOMAIN)
                           or name contains illegal characters (if checking)
+                          or name is an IP address (for IP address lookup)
             DNS_NODATA    domain exists, but no data for this type (NODATA)
             DNS_AGAIN     soft failure, try again later
             DNS_FAIL      DNS failure
@@ -333,9 +545,10 @@ int
 dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
 {
 #ifndef STAND_ALONE
-int rc;
+int rc = -1;
 uschar *save;
 #endif
+res_state resp = os_get_dns_resolver_res();
 
 tree_node *previous;
 uschar node_name[290];
@@ -346,7 +559,7 @@ have many addresses in the same domain. We rely on the resolver and name server
 caching for successful lookups. */
 
 sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
-  _res.options);
+  resp->options);
 previous = tree_search(tree_dns_fails, node_name);
 if (previous != NULL)
   {
@@ -359,35 +572,6 @@ if (previous != NULL)
   return previous->data.val;
   }
 
-/* If we are running in the test harness, recognize a couple of special
-names that always give error returns. This makes it straightforward to
-test the handling of DNS errors. */
-
-if (running_in_test_harness)
-  {
-  uschar *endname = name + Ustrlen(name);
-  if (Ustrcmp(endname - 14, "test.again.dns") == 0)
-    {
-    int delay = Uatoi(name);  /* digits at the start of the name */
-    DEBUG(D_dns) debug_printf("Real DNS lookup of %s (%s) bypassed for testing\n",
-      name, dns_text_type(type));
-    if (delay > 0)
-      {
-      DEBUG(D_dns) debug_printf("delaying %d seconds\n", delay);
-      sleep(delay);
-      }
-    DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
-    return dns_return(name, type, DNS_AGAIN);
-    }
-  if (Ustrcmp(endname - 13, "test.fail.dns") == 0)
-    {
-    DEBUG(D_dns) debug_printf("Real DNS lookup of %s (%s) bypassed for testing\n",
-      name, dns_text_type(type));
-    DEBUG(D_dns) debug_printf("returning DNS_FAIL\n");
-    return dns_return(name, type, DNS_FAIL);
-    }
-  }
-
 /* If configured, check the hygene of the name passed to lookup. Otherwise,
 although DNS lookups may give REFUSED at the lower level, some resolvers
 turn this into TRY_AGAIN, which is silly. Give a NOMATCH return, since such
@@ -404,7 +588,7 @@ For SRV records, we omit the initial _smtp._tcp. components at the start. */
 
 #ifndef STAND_ALONE   /* Omit this for stand-alone tests */
 
-if (check_dns_names_pattern[0] != 0 && type != T_PTR)
+if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
   uschar *checkname = name;
   int ovector[3*(EXPAND_MAXN+1)];
@@ -436,11 +620,37 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR)
 #endif /* STAND_ALONE */
 
 /* Call the resolver; for an overlong response, res_search() will return the
-number of bytes the message would need, so we need to check for this case.
-The effect is to truncate overlong data. */
+number of bytes the message would need, so we need to check for this case. The
+effect is to truncate overlong data.
+
+On some systems, res_search() will recognize "A-for-A" queries and return
+the IP address instead of returning -1 with h_error=HOST_NOT_FOUND. Some
+nameservers are also believed to do this. It is, of course, contrary to the
+specification of the DNS, so we lock it out. */
+
+if ((
+    #ifdef SUPPORT_A6
+    type == T_A6 ||
+    #endif
+    type == T_A || type == T_AAAA) &&
+    string_is_ip_address(name, NULL) != 0)
+  return DNS_NOMATCH;
+
+/* If we are running in the test harness, instead of calling the normal resolver
+(res_search), we call fakens_search(), which recognizes certain special
+domains, and interfaces to a fake nameserver for certain special zones. */
 
-dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
-if (dnsa->answerlen > MAXPACKET) dnsa->answerlen = MAXPACKET;
+if (running_in_test_harness)
+  dnsa->answerlen = fakens_search(name, type, dnsa->answer, MAXPACKET);
+else
+  dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
+
+if (dnsa->answerlen > MAXPACKET)
+  {
+  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet (size %d), truncating to %d.\n",
+    name, dns_text_type(type), dnsa->answerlen, MAXPACKET);
+  dnsa->answerlen = MAXPACKET;
+  }
 
 if (dnsa->answerlen < 0) switch (h_errno)
   {
@@ -569,12 +779,10 @@ for (i = 0; i < 10; i++)
     else if (rr->type == T_CNAME) cname_rr = *rr;
     }
 
-  /* If a CNAME was found, take the fully qualified name from it; otherwise
-  from the first data record, if present. For testing, there is a magic name
-  that gets its casing adjusted, because my resolver doesn't seem to pass back
-  upper case letters in domain names. */
+  /* For the first time round this loop, if a CNAME was found, take the fully
+  qualified name from it; otherwise from the first data record, if present. */
 
-  if (fully_qualified_name != NULL)
+  if (i == 0 && fully_qualified_name != NULL)
     {
     if (cname_rr.data != NULL)
       {
@@ -584,15 +792,9 @@ for (i = 0; i < 10; i++)
       }
     else if (type_rr.data != NULL)
       {
-      if (running_in_test_harness &&
-          Ustrcmp(type_rr.name, "uppercase.test.ex") == 0)
-        *fully_qualified_name = US"UpperCase.test.ex";
-      else
-        {
-        if (Ustrcmp(type_rr.name, *fully_qualified_name) != 0 &&
-            type_rr.name[0] != '*')
-          *fully_qualified_name = string_copy_dnsdomain(type_rr.name);
-        }
+      if (Ustrcmp(type_rr.name, *fully_qualified_name) != 0 &&
+          type_rr.name[0] != '*')
+        *fully_qualified_name = string_copy_dnsdomain(type_rr.name);
       }
     }
 
@@ -610,6 +812,8 @@ for (i = 0; i < 10; i++)
     cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256);
   if (datalen < 0) return DNS_FAIL;
   name = data;
+
+  DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name);
   }       /* Loop back to do another lookup */
 
 /*Control reaches here after 10 times round the CNAME loop. Something isn't