X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fdns.c;h=fc0ffb2baa59e2b1e09582b2c24943da5cd0d82a;hp=27ab2d2a237984469a17f990392734c03d2a980c;hb=4fab92fbc2b63bac2d89c1dae69fa1845cb640b7;hpb=94431adbd61d7706fe6df3a19bcae043fec950bf diff --git a/src/src/dns.c b/src/src/dns.c index 27ab2d2a2..fc0ffb2ba 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for interfacing with the DNS. */ @@ -305,6 +305,15 @@ else +/* 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 * *************************************************/ @@ -323,15 +332,24 @@ 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) { + TRACE debug_printf("%s: reset\n", __FUNCTION__); dnss->rrcount = ntohs(h->qdcount); dnss->aptr = dnsa->answer + sizeof(HEADER); @@ -339,10 +357,13 @@ if (reset != RESET_NEXT) 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. */ @@ -353,23 +374,37 @@ if (reset != RESET_NEXT) 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); + } 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: %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. */ @@ -379,51 +414,66 @@ if (dnss->rrcount-- <= 0) return NULL; /* 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 += 2; /* 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 */ -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\n", + __FUNCTION__, dnss->rrcount, trace); + dnss->rrcount = 0; + return NULL; } -/* Extract the AUTHORITY information from the answer. If the -answer isn't authoritive (AA not set), we do not extract anything. +/* Extract the AUTHORITY information from the answer. If the answer isn't +authoritive (AA not set), we do not extract anything. + +The AUTHORITIVE 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?) -The AUTHORITIVE 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. -We've cycle through the 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; -HEADER * h = (HEADER *) dnsa->answer; +const HEADER * h = (const HEADER *) dnsa->answer; -if (!h->nscount || !h->aa) return NULL; -for (rr = dns_next_rr((dns_answer*) dnsa, &dnss, RESET_AUTHORITY); - rr; - rr = dns_next_rr((dns_answer*) dnsa, &dnss, RESET_NEXT)) - if (rr->type == (h->ancount ? T_NS : T_SOA)) return rr->name; +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; } @@ -450,7 +500,7 @@ DEBUG(D_dns) debug_printf("DNSSEC support disabled at build-time; dns_is_secure() false\n"); return FALSE; #else -HEADER * h = (HEADER *) dnsa->answer; +const HEADER * h = (const HEADER *) dnsa->answer; const uschar * auth_name; const uschar * trusted; @@ -483,8 +533,10 @@ return TRUE; 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 } /************************************************ @@ -496,7 +548,11 @@ h->ad = 0; 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 } @@ -558,7 +614,7 @@ dns_return(const 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), - resp->options); + (unsigned long) resp->options); node->data.val = rc; (void)tree_insertnode(&tree_dns_fails, node); return rc; @@ -607,9 +663,8 @@ 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), - 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), @@ -620,7 +675,7 @@ if (previous != NULL) return previous->data.val; } -#ifdef EXPERIMENTAL_INTERNATIONAL +#ifdef SUPPORT_I18N /* Convert all names to a-label form before doing lookup */ { uschar * alabel; @@ -714,48 +769,48 @@ if (dnsa->answerlen > MAXPACKET) 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", @@ -813,14 +868,15 @@ BOOL secure_so_far = TRUE; 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 @@ -830,28 +886,26 @@ for (i = 0; i < 10; i++) 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 @@ -863,7 +917,7 @@ for (i = 0; i < 10; i++) /* 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); @@ -875,10 +929,13 @@ for (i = 0; i < 10; i++) 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)) @@ -1025,7 +1082,7 @@ switch (type) 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 @@ -1033,8 +1090,7 @@ switch (type) 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 || @@ -1069,13 +1125,11 @@ switch (type) 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); @@ -1112,8 +1166,7 @@ return DNS_FAIL; * 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 @@ -1145,8 +1198,11 @@ else { 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; } }