* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for interfacing with the DNS. */
{
int len = Ustrlen(domain);
int asize = size; /* Locally modified */
-uschar *endname;
uschar name[256];
uschar utilname[256];
uschar *aptr = answerptr; /* Locally modified */
if (domain[len - 1] == '.') len--;
Ustrncpy(name, domain, len);
name[len] = 0;
-endname = name + len;
/* Look for the fakens utility, and if it exists, call it. */
asize -= rc; /* may need to be passed on to res_search(). */
}
- /* If we ran out of output buffer before exhasting the return,
+ /* If we ran out of output buffer before exhausting the return,
carry on reading and counting it. */
if (asize == 0)
+/* Increment the aptr in dnss, checking against dnsa length.
+Return: TRUE for a bad result
+*/
+static BOOL
+dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta)
+{
+return (dnss->aptr += delta) >= dnsa->answer + dnsa->answerlen;
+}
+
/*************************************************
* Get next DNS record from answer block *
*************************************************/
Arguments:
dnsa pointer to dns answer block
dnss pointer to dns scan block
- reset option specifing what portion to scan, as described above
+ reset option specifying what portion to scan, as described above
Returns: next dns record, or NULL when no more
*/
dns_record *
-dns_next_rr(dns_answer *dnsa, dns_scan *dnss, int reset)
+dns_next_rr(const dns_answer *dnsa, dns_scan *dnss, int reset)
{
-HEADER *h = (HEADER *)dnsa->answer;
+const HEADER * h = (const HEADER *)dnsa->answer;
int namelen;
+char * trace = NULL;
+#ifdef rr_trace
+# define TRACE DEBUG(D_dns)
+#else
+trace = trace;
+# define TRACE if (FALSE)
+#endif
+
/* Reset the saved data when requested to, and skip to the first required RR */
if (reset != RESET_NEXT)
{
dnss->rrcount = ntohs(h->qdcount);
+ TRACE debug_printf("%s: reset (Q rrcount %d)\n", __FUNCTION__, dnss->rrcount);
dnss->aptr = dnsa->answer + sizeof(HEADER);
/* Skip over questions; failure to expand the name just gives up */
while (dnss->rrcount-- > 0)
{
+ TRACE trace = "Q-namelen";
namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- dnss->aptr, (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
- if (namelen < 0) { dnss->rrcount = 0; return NULL; }
- dnss->aptr += namelen + 4; /* skip name & type & class */
+ dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+ if (namelen < 0) goto null_return;
+ /* skip name & type & class */
+ TRACE trace = "Q-skip";
+ if (dnss_inc_aptr(dnsa, dnss, namelen+4)) goto null_return;
}
/* Get the number of answer records. */
dnss->rrcount = ntohs(h->ancount);
+ TRACE debug_printf("%s: reset (A rrcount %d)\n", __FUNCTION__, dnss->rrcount);
/* 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) dnss->rrcount += ntohs(h->nscount);
+ if (reset == RESET_ADDITIONAL)
+ {
+ TRACE debug_printf("%s: additional\n", __FUNCTION__);
+ dnss->rrcount += ntohs(h->nscount);
+ TRACE debug_printf("%s: reset (NS rrcount %d)\n", __FUNCTION__, dnss->rrcount);
+ }
if (reset == RESET_AUTHORITY || reset == RESET_ADDITIONAL)
{
+ TRACE if (reset == RESET_AUTHORITY)
+ debug_printf("%s: authority\n", __FUNCTION__);
while (dnss->rrcount-- > 0)
{
+ TRACE trace = "A-namelen";
namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- dnss->aptr, (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
- if (namelen < 0) { dnss->rrcount = 0; return NULL; }
- dnss->aptr += namelen + 8; /* skip name, type, class & TTL */
+ dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+ if (namelen < 0) goto null_return;
+ /* skip name, type, class & TTL */
+ TRACE trace = "A-hdr";
+ if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return;
GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
- dnss->aptr += dnss->srr.size; /* skip over it */
+ /* skip over it */
+ TRACE trace = "A-skip";
+ if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return;
}
- dnss->rrcount = (reset == RESET_AUTHORITY)
+ dnss->rrcount = reset == RESET_AUTHORITY
? ntohs(h->nscount) : ntohs(h->arcount);
+ TRACE debug_printf("%s: reset (%s rrcount %d)\n", __FUNCTION__,
+ reset == RESET_AUTHORITY ? "NS" : "AR", dnss->rrcount);
}
+ TRACE debug_printf("%s: %d RRs to read\n", __FUNCTION__, dnss->rrcount);
}
+else
+ TRACE debug_printf("%s: next (%d left)\n", __FUNCTION__, dnss->rrcount);
/* The variable dnss->aptr is now pointing at the next RR, and dnss->rrcount
contains the number of RR records left. */
/* If expanding the RR domain name fails, behave as if no more records
(something safe). */
+TRACE trace = "R-namelen";
namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr,
- (DN_EXPAND_ARG4_TYPE) &(dnss->srr.name), DNS_MAXNAME);
-if (namelen < 0) { dnss->rrcount = 0; return NULL; }
+ (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+if (namelen < 0) goto null_return;
/* Move the pointer past the name and fill in the rest of the data structure
from the following bytes. */
-dnss->aptr += namelen;
-GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */
-dnss->aptr += 6; /* Don't want class or TTL */
-GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */
-dnss->srr.data = dnss->aptr; /* The record's data follows */
-dnss->aptr += dnss->srr.size; /* Advance to next RR */
+TRACE trace = "R-name";
+if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
+
+GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */
+TRACE trace = "R-class";
+if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return; /* Don't want class */
+GETLONG(dnss->srr.ttl, dnss->aptr); /* TTL */
+GETSHORT(dnss->srr.size, dnss->aptr); /* Size of data portion */
+dnss->srr.data = dnss->aptr; /* The record's data follows */
+
+/* Unchecked increment ok here since no further access on this iteration;
+will be checked on next at "R-name". */
+
+dnss->aptr += dnss->srr.size; /* Advance to next RR */
/* Return a pointer to the dns_record structure within the dns_answer. This is
for convenience so that the scans can use nice-looking for loops. */
-return &(dnss->srr);
+return &dnss->srr;
+
+null_return:
+ TRACE debug_printf("%s: terminate (%d RRs left). Last op: %s; errno %d %s\n",
+ __FUNCTION__, dnss->rrcount, trace, errno, strerror(errno));
+ dnss->rrcount = 0;
+ return NULL;
+}
+
+
+/* Extract the AUTHORITY information from the answer. If the answer isn't
+authoritative (AA not set), we do not extract anything.
+
+The AUTHORITY section contains NS records if the name in question was found,
+it contains a SOA record otherwise. (This is just from experience and some
+tests, is there some spec?)
+
+Scan the whole AUTHORITY section, since it may contain other records
+(e.g. NSEC3) too.
+
+Return: name for the authority, in an allocated string, or NULL if none found */
+
+static const uschar *
+dns_extract_auth_name(const dns_answer * dnsa) /* FIXME: const dns_answer */
+{
+dns_scan dnss;
+dns_record * rr;
+const HEADER * h = (const HEADER *) dnsa->answer;
+
+if (h->nscount && h->aa)
+ for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
+ rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+ if (rr->type == (h->ancount ? T_NS : T_SOA))
+ return string_copy(rr->name);
+return NULL;
}
/* 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)
+(AD = Authentic Data, AA = Authoritative Answer)
Argument: pointer to dns answer block
Returns: bool indicating presence of AD bit
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;
+const HEADER * h = (const HEADER *) dnsa->answer;
+const uschar * auth_name;
+const uschar * trusted;
+
+if (h->ad) return TRUE;
+
+/* If the resolver we ask is authoritative for the domain in question, it
+* may not set the AD but the AA bit. If we explicitly trust
+* the resolver for that domain (via a domainlist in dns_trust_aa),
+* we return TRUE to indicate a secure answer.
+*/
+
+if ( !h->aa
+ || !dns_trust_aa
+ || !(trusted = expand_string(dns_trust_aa))
+ || !*trusted
+ || !(auth_name = dns_extract_auth_name(dnsa))
+ || OK != match_isinlist(auth_name, &trusted, 0, NULL, NULL,
+ MCL_DOMAIN, TRUE, NULL)
+ )
+ return FALSE;
+
+DEBUG(D_dns) debug_printf("DNS faked the AD bit "
+ "(got AA and matched with dns_trust_aa (%s in %s))\n",
+ auth_name, dns_trust_aa);
+
+return TRUE;
#endif
}
static void
dns_set_insecure(dns_answer * dnsa)
{
+#ifndef DISABLE_DNSSEC
HEADER * h = (HEADER *)dnsa->answer;
-h->ad = 0;
+h->aa = h->ad = 0;
+#endif
}
/************************************************
* Check whether the AA bit is set *
* We need this to warn if we requested AD *
- * from a authoritive server *
+ * from an authoritative server *
************************************************/
BOOL
dns_is_aa(const dns_answer *dnsa)
{
-return ((HEADER*)dnsa->answer)->aa;
+#ifdef DISABLE_DNSSEC
+return FALSE;
+#else
+return ((const HEADER*)dnsa->answer)->aa;
+#endif
}
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),
- resp->options);
+ (unsigned long) resp->options);
node->data.val = rc;
(void)tree_insertnode(&tree_dns_fails, node);
return rc;
}
-
-
/*************************************************
* Do basic DNS lookup *
*************************************************/
/* Call the resolver to look up the given domain name, using the given type,
and check the result. The error code TRY_AGAIN is documented as meaning "non-
-Authoritive Host not found, or SERVERFAIL". Sometimes there are badly set
+Authoritative Host not found, or SERVERFAIL". Sometimes there are badly set
up nameservers that produce this error continually, so there is the option of
providing a list of domains for which this is treated as a non-existent
host.
caching for successful lookups. */
sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
- resp->options);
-previous = tree_search(tree_dns_fails, node_name);
-if (previous != NULL)
+ (unsigned long) resp->options);
+if ((previous = tree_search(tree_dns_fails, node_name)))
{
DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
name, dns_text_type(type),
return previous->data.val;
}
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
/* Convert all names to a-label form before doing lookup */
{
uschar * alabel;
}
#endif
-/* If configured, check the hygene of the name passed to lookup. Otherwise,
+/* If configured, check the hygiene 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
domains cannot be in the DNS. The check is now done by a regular expression;
}
if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
- 0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0)
+ 0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
{
DEBUG(D_dns)
debug_printf("DNS name syntax check failed: %s (%s)\n", name,
domains, and interfaces to a fake nameserver for certain special zones. */
dnsa->answerlen = running_in_test_harness
- ? fakens_search(name, type, dnsa->answer, MAXPACKET)
- : res_search(CCS name, C_IN, type, dnsa->answer, MAXPACKET);
+ ? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer))
+ : res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer));
-if (dnsa->answerlen > MAXPACKET)
+if (dnsa->answerlen > (int) sizeof(dnsa->answer))
{
- 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;
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet"
+ " (size %d), truncating to %u.\n",
+ name, dns_text_type(type), dnsa->answerlen, (unsigned int) sizeof(dnsa->answer));
+ dnsa->answerlen = sizeof(dnsa->answer);
}
if (dnsa->answerlen < 0) switch (h_errno)
{
case HOST_NOT_FOUND:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
- "returning DNS_NOMATCH\n", name, dns_text_type(type));
- return dns_return(name, type, DNS_NOMATCH);
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
+ "returning DNS_NOMATCH\n", name, dns_text_type(type));
+ return dns_return(name, type, DNS_NOMATCH);
case TRY_AGAIN:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
- name, dns_text_type(type));
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
+ name, dns_text_type(type));
- /* Cut this out for various test programs */
+ /* Cut this out for various test programs */
#ifndef STAND_ALONE
- save_domain = deliver_domain;
- deliver_domain = string_copy(name); /* set $domain */
- rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
- MCL_DOMAIN, TRUE, NULL);
- deliver_domain = save_domain;
- if (rc != OK)
- {
- DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
- return dns_return(name, type, DNS_AGAIN);
- }
- DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
- "DNS_NOMATCH\n", name);
- return dns_return(name, type, DNS_NOMATCH);
+ save_domain = deliver_domain;
+ deliver_domain = string_copy(name); /* set $domain */
+ rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
+ MCL_DOMAIN, TRUE, NULL);
+ deliver_domain = save_domain;
+ if (rc != OK)
+ {
+ DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
+ return dns_return(name, type, DNS_AGAIN);
+ }
+ DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
+ "DNS_NOMATCH\n", name);
+ return dns_return(name, type, DNS_NOMATCH);
#else /* For stand-alone tests */
- return dns_return(name, type, DNS_AGAIN);
+ return dns_return(name, type, DNS_AGAIN);
#endif
case NO_RECOVERY:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
- "returning DNS_FAIL\n", name, dns_text_type(type));
- return dns_return(name, type, DNS_FAIL);
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
+ "returning DNS_FAIL\n", name, dns_text_type(type));
+ return dns_return(name, type, DNS_FAIL);
case NO_DATA:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
- "returning DNS_NODATA\n", name, dns_text_type(type));
- return dns_return(name, type, DNS_NODATA);
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
+ "returning DNS_NODATA\n", name, dns_text_type(type));
+ return dns_return(name, type, DNS_NODATA);
default:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
- "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
- return dns_return(name, type, DNS_FAIL);
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
+ "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
+ return dns_return(name, type, DNS_FAIL);
}
DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
for (i = 0; i < 10; i++)
{
- uschar data[256];
+ uschar * data;
dns_record *rr, cname_rr, type_rr;
dns_scan dnss;
int datalen, rc;
/* DNS lookup failures get passed straight back. */
- if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED) return rc;
+ if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED)
+ return rc;
/* We should have either records of the required type, or a CNAME record,
or both. We need to know whether both exist for getting the fully qualified
cname_rr.data = type_rr.data = NULL;
for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
- rr;
- rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
- {
+ rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
if (rr->type == type)
{
if (type_rr.data == NULL) type_rr = *rr;
if (cname_rr.data != NULL) break;
}
- else if (rr->type == T_CNAME) cname_rr = *rr;
- }
+ else if (rr->type == T_CNAME)
+ cname_rr = *rr;
/* 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 (i == 0 && fully_qualified_name != NULL)
+ if (i == 0 && fully_qualified_name)
{
- uschar * rr_name = cname_rr.data ? cname_rr.name
- : type_rr.data ? type_rr.name : NULL;
+ uschar * rr_name = cname_rr.data
+ ? cname_rr.name : type_rr.data ? type_rr.name : NULL;
if ( rr_name
&& Ustrcmp(rr_name, *fully_qualified_name) != 0
&& rr_name[0] != '*'
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
&& ( !string_is_utf8(*fully_qualified_name)
|| Ustrcmp(rr_name,
string_domain_utf8_to_alabel(*fully_qualified_name, NULL)) != 0
/* If any data records of the correct type were found, we are done. */
- if (type_rr.data != NULL)
+ if (type_rr.data)
{
if (!secure_so_far) /* mark insecure if any element of CNAME chain was */
dns_set_insecure(dnsa);
have had a failure from dns_lookup). However code against the possibility of
its not existing. */
- if (cname_rr.data == NULL) return DNS_FAIL;
- datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, sizeof(data));
- if (datalen < 0) return DNS_FAIL;
+ if (!cname_rr.data)
+ return DNS_FAIL;
+
+ data = store_get(256);
+ if ((datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+ cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256)) < 0)
+ return DNS_FAIL;
name = data;
if (!dns_is_secure(dnsa))
* Do a DNS lookup and handle virtual types *
************************************************/
-/* This function handles some invented "lookup types" that synthesize feature
+/* This function handles some invented "lookup types" that synthesize features
not available in the basic types. The special types all have negative values.
Positive type values are passed straight on to dns_lookup().
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;
+ const HEADER * h = (const 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
dnsa->answerlen = MAXPACKET;
for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
- rr;
- rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
)
if (rr->type != T_SOA) continue;
else if (strcmpic(rr->name, US"") == 0 ||
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))
+ rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
{
- if (rr->type != T_SRV) continue;
+ const uschar * p = rr->data;
/* Extract the numerical SRV fields (p is incremented) */
- p = rr->data;
GETSHORT(priority, p);
GETSHORT(weight, p); weight = weight; /* compiler quietening */
GETSHORT(port, p);
* Get address(es) from DNS record *
*************************************************/
-/* The record type is either T_A for an IPv4 address or T_AAAA (or T_A6 when
-supported) for an IPv6 address.
+/* The record type is either T_A for an IPv4 address or T_AAAA for an IPv6 address.
Argument:
dnsa the DNS answer block
{
if (rr->data + 16 <= dnsa_lim)
{
+ struct in6_addr in6;
+ int i;
+ for (i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i];
yield = store_get(sizeof(dns_address) + 50);
- inet_ntop(AF_INET6, US rr->data, CS yield->address, 50);
+ inet_ntop(AF_INET6, &in6, CS yield->address, 50);
yield->next = NULL;
}
}