-/* $Cambridge: exim/src/src/dns.c,v 1.11 2005/09/16 14:44:11 ph10 Exp $ */
-
/*************************************************
* 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. */
/* 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, 10.in-addr.arpa, and 0.8.e.f.ip6.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
test zones, whereas the new test suit has the fake server for portability. This
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 */
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) ||
- (len >= 17 && Ustrcmp(endname - 17, ".0.8.e.f.ip6.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);
+DEBUG(D_dns) debug_printf("passing %s on to res_search()\n", domain);
return res_search(CS domain, C_IN, type, answerptr, size);
}
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_use_dnssec >= 0)
+ {
+ if (dns_use_edns0 == 0 && dns_use_dnssec != 0)
+ {
+ DEBUG(D_resolver)
+ debug_printf("CONFLICT: dns_use_edns0 forced false, dns_use_dnssec forced true!\n");
+ }
+ else
+ {
+ if (dns_use_dnssec)
+ resp->options |= RES_USE_DNSSEC;
+ else
+ resp->options &= ~RES_USE_DNSSEC;
+ DEBUG(D_resolver) debug_printf("Coerced resolver DNSSEC support %s.\n",
+ dns_use_dnssec ? "on" : "off");
+ }
+ }
+# else
+if (dns_use_dnssec >= 0)
+ DEBUG(D_resolver)
+ debug_printf("Unable to %sset DNSSEC without resolver support.\n",
+ dns_use_dnssec ? "" : "un");
+# endif
+#endif /* DISABLE_DNSSEC */
+
+os_put_dns_resolver_res(resp);
}
+/*************************************************
+* 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 *
*************************************************/
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";
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;
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
int
dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
{
-int rc = -1;
#ifndef STAND_ALONE
+int rc = -1;
uschar *save;
#endif
+res_state resp = os_get_dns_resolver_res();
tree_node *previous;
uschar node_name[290];
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)
{
#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)];
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. */
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)
{
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)
{
}
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);
}
}
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