GnuTLS control constants exposed to Makefile.
[exim.git] / src / src / dns.c
index 83daf50f39a902dac92071ff80a644e7c791d5da..ae76e9e3f89277903f5f3512f4dd95af48bc8e0d 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/dns.c,v 1.10 2005/09/13 15:40:07 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
@@ -29,17 +27,17 @@ static void dns_complete_a6(dns_address ***, dns_answer *, dns_record *,
 
 /* 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
 
 /* 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). It also recognises the
-zones test.ex and 10.in-addr.arpa, and for those it calls an external utility
-that mock-up a nameserver, if it can find the utility. Otherwise, it passes its
-arguments on to res_search().
+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
 
 Background: the original test suite required a real nameserver to carry the
-test.ex and 10.in-addr.arpa zones, whereas the new test suit has the fake
-server for portability. This code supports both.
+test zones, whereas the new test suit has the fake server for portability. This
+code supports both.
 
 Arguments:
 
 Arguments:
-  name        the domain name
+  domain      the domain name
   type        the DNS record type
   answerptr   where to put the answer
   size        size of the answer area
   type        the DNS record type
   answerptr   where to put the answer
   size        size of the answer area
@@ -48,10 +46,26 @@ Returns:      length of returned data, or -1 on error (h_errno set)
 */
 
 static int
 */
 
 static int
-fakens_search(uschar *name, int type, uschar *answerptr, int size)
+fakens_search(uschar *domain, int type, uschar *answerptr, int size)
 {
 {
-int len = Ustrlen(name);
-uschar *endname = name + len;
+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)
   {
 
 if (len >= 14 && Ustrcmp(endname - 14, "test.again.dns") == 0)
   {
@@ -75,65 +89,62 @@ if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0)
   return -1;
   }
 
   return -1;
   }
 
-if (Ustrcmp(name, "test.ex") == 0 ||
-    (len > 8 && Ustrcmp(endname - 8, ".test.ex") == 0) ||
-    (len >= 16 && Ustrcmp(endname - 16, ".10.in-addr.arpa") == 0))
+/* 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)
   {
   {
-  uschar utilname[256];
-  struct stat statbuf;
+  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));
 
 
-  (void)string_format(utilname, sizeof(utilname), "%s/../bin/fakens",
-    spool_directory);
+  argv[0] = utilname;
+  argv[1] = spool_directory;
+  argv[2] = name;
+  argv[3] = dns_text_type(type);
+  argv[4] = NULL;
 
 
-  if (stat(CS utilname, &statbuf) >= 0)
+  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)
     {
     {
-    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 (size > 0 && (rc = read(outfd, answerptr, size)) > 0)
-      {
-      len += rc;
-      answerptr += rc;
-      size -= rc;
-      }
+    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));
+  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; break;
-      case 2: h_errno = TRY_AGAIN; break;
-      default:
-      case 3: h_errno = NO_RECOVERY; break;
-      case 4: h_errno = NO_DATA; break;
-      }
-    return -1;
+  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");
     }
   }
 
     }
   }
 
-/* Not test.ex or 10.in-addr.arpa, or fakens utility not found. */
+/* 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 name, C_IN, type, answerptr, size);
+return res_search(CS domain, C_IN, type, answerptr, size);
 }
 
 
 }
 
 
@@ -155,18 +166,42 @@ Returns:            nothing
 void
 dns_init(BOOL qualify_single, BOOL search_parents)
 {
 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();
   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);
                 (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
+
+os_put_dns_resolver_res(resp);
 }
 
 
 }
 
 
@@ -380,6 +415,7 @@ switch(t)
   case T_AAAA:  return US"AAAA";
   case T_A6:    return US"A6";
   case T_TXT:   return US"TXT";
   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_PTR:   return US"PTR";
   case T_SOA:   return US"SOA";
   case T_SRV:   return US"SRV";
@@ -411,9 +447,10 @@ Returns:     the return code
 static int
 dns_return(uschar *name, int type, int rc)
 {
 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),
 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;
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
 return rc;
@@ -440,6 +477,7 @@ Arguments:
 Returns:    DNS_SUCCEED   successful lookup
             DNS_NOMATCH   name not found (NXDOMAIN)
                           or name contains illegal characters (if checking)
 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
             DNS_NODATA    domain exists, but no data for this type (NODATA)
             DNS_AGAIN     soft failure, try again later
             DNS_FAIL      DNS failure
@@ -448,10 +486,11 @@ Returns:    DNS_SUCCEED   successful lookup
 int
 dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
 {
 int
 dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
 {
-int rc = -1;
 #ifndef STAND_ALONE
 #ifndef STAND_ALONE
+int rc = -1;
 uschar *save;
 #endif
 uschar *save;
 #endif
+res_state resp = os_get_dns_resolver_res();
 
 tree_node *previous;
 uschar node_name[290];
 
 tree_node *previous;
 uschar node_name[290];
@@ -462,7 +501,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),
 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)
   {
 previous = tree_search(tree_dns_fails, node_name);
 if (previous != NULL)
   {
@@ -491,7 +530,7 @@ For SRV records, we omit the initial _smtp._tcp. components at the start. */
 
 #ifndef STAND_ALONE   /* Omit this for stand-alone tests */
 
 
 #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)];
   {
   uschar *checkname = name;
   int ovector[3*(EXPAND_MAXN+1)];
@@ -526,7 +565,20 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR)
 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.
 
-If we are running in the test harness, instead of calling the normal resolver
+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. */
 
 (res_search), we call fakens_search(), which recognizes certain special
 domains, and interfaces to a fake nameserver for certain special zones. */
 
@@ -535,7 +587,12 @@ if (running_in_test_harness)
 else
   dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
 
 else
   dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
 
-if (dnsa->answerlen > MAXPACKET) dnsa->answerlen = 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)
   {
 
 if (dnsa->answerlen < 0) switch (h_errno)
   {
@@ -664,12 +721,10 @@ for (i = 0; i < 10; i++)
     else if (rr->type == T_CNAME) cname_rr = *rr;
     }
 
     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)
       {
     {
     if (cname_rr.data != NULL)
       {
@@ -679,15 +734,9 @@ for (i = 0; i < 10; i++)
       }
     else if (type_rr.data != NULL)
       {
       }
     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);
       }
     }
 
       }
     }
 
@@ -705,6 +754,8 @@ for (i = 0; i < 10; i++)
     cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256);
   if (datalen < 0) return DNS_FAIL;
   name = data;
     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
   }       /* Loop back to do another lookup */
 
 /*Control reaches here after 10 times round the CNAME loop. Something isn't