X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Fdns.c;h=f492613c4785f0e8141718cd94198ef7469316cb;hb=e0eb00cd78f2dbf91aba5dffa579177b1e1815a1;hp=7395854ff9f17db3ac8be1bff26b4a413f2cfbb1;hpb=e7726cbf334d1782435662c2d209ad82ec602c5c;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/dns.c b/src/src/dns.c index 7395854ff..f492613c4 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -1,10 +1,8 @@ -/* $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 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for interfacing with the DNS. */ @@ -29,10 +27,10 @@ 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 -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 @@ -48,17 +46,27 @@ Returns: length of returned data, or -1 on error (h_errno set) */ static int -fakens_search(uschar *domain, int type, uschar *answerptr, int size) +fakens_search(const 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 */ @@ -81,66 +89,60 @@ if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0) 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)); + + argv[0] = utilname; + argv[1] = spool_directory; + argv[2] = name; + argv[3] = dns_text_type(type); + argv[4] = NULL; - (void)string_format(utilname, sizeof(utilname), "%s/../bin/fakens", - spool_directory); + 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)); - if (stat(CS utilname, &statbuf) >= 0) + 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); } @@ -157,25 +159,85 @@ the first time we have been here, and set the resolver options. Arguments: qualify_single TRUE to set the RES_DEFNAMES option search_parents TRUE to set the RES_DNSRCH option + use_dnssec TRUE to set the RES_USE_DNSSEC option Returns: nothing */ void -dns_init(BOOL qualify_single, BOOL search_parents) +dns_init(BOOL qualify_single, BOOL search_parents, BOOL use_dnssec) { -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 (use_dnssec) + resp->options |= RES_USE_DNSSEC; +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"); +if (use_dnssec) + DEBUG(D_resolver) + debug_printf("Unable to set DNSSEC without resolver support.\n"); +# endif +#endif /* DISABLE_DNSSEC */ + +os_put_dns_resolver_res(resp); } @@ -368,6 +430,41 @@ 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(const 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 +} + +static void +dns_set_insecure(dns_answer * dnsa) +{ +HEADER * h = (HEADER *)dnsa->answer; +h->ad = 0; +} + + + + /************************************************* * Turn DNS type into text * *************************************************/ @@ -389,11 +486,13 @@ 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"; + case T_TLSA: return US"TLSA"; default: return US"?"; } } @@ -418,11 +517,12 @@ Returns: the return code */ static int -dns_return(uschar *name, int type, int rc) +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), - _res.options); + resp->options); node->data.val = rc; (void)tree_insertnode(&tree_dns_fails, node); return rc; @@ -449,18 +549,20 @@ 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 */ int -dns_basic_lookup(dns_answer *dnsa, uschar *name, int type) +dns_basic_lookup(dns_answer *dnsa, const 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]; @@ -471,7 +573,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) { @@ -500,9 +602,9 @@ 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; + const uschar *checkname = name; int ovector[3*(EXPAND_MAXN+1)]; if (regex_check_dns_names == NULL) @@ -512,13 +614,13 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR) /* For an SRV lookup, skip over the first two components (the service and protocol names, which both start with an underscore). */ - if (type == T_SRV) + if (type == T_SRV || type == T_TLSA) { while (*checkname++ != '.'); while (*checkname++ != '.'); } - if (pcre_exec(regex_check_dns_names, NULL, CS checkname, Ustrlen(checkname), + if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname), 0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0) { DEBUG(D_dns) @@ -535,16 +637,34 @@ 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. -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. */ 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); + dnsa->answerlen = res_search(CCS 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) { @@ -558,9 +678,9 @@ if (dnsa->answerlen < 0) switch (h_errno) name, dns_text_type(type)); /* Cut this out for various test programs */ - #ifndef STAND_ALONE +#ifndef STAND_ALONE save = deliver_domain; - deliver_domain = name; /* set $domain */ + deliver_domain = string_copy(name); /* set $domain */ rc = match_isinlist(name, &dns_again_means_nonexist, 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL); deliver_domain = save; @@ -573,9 +693,9 @@ if (dnsa->answerlen < 0) switch (h_errno) "DNS_NOMATCH\n", name); return dns_return(name, type, DNS_NOMATCH); - #else /* For stand-alone tests */ +#else /* For stand-alone tests */ return dns_return(name, type, DNS_AGAIN); - #endif +#endif case NO_RECOVERY: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n" @@ -636,10 +756,12 @@ Returns: DNS_SUCCEED successful lookup */ int -dns_lookup(dns_answer *dnsa, uschar *name, int type, uschar **fully_qualified_name) +dns_lookup(dns_answer *dnsa, const uschar *name, int type, + uschar **fully_qualified_name) { int i; -uschar *orig_name = name; +const uschar *orig_name = name; +BOOL secure_so_far = TRUE; /* Loop to follow CNAME chains so far, but no further... */ @@ -673,12 +795,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) { @@ -688,21 +808,20 @@ 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); } } /* If any data records of the correct type were found, we are done. */ - if (type_rr.data != NULL) return DNS_SUCCEED; + if (type_rr.data != NULL) + { + if (!secure_so_far) /* mark insecure if any element of CNAME chain was */ + dns_set_insecure(dnsa); + return DNS_SUCCEED; + } /* If there are no data records, we need to re-scan the DNS using the domain given in the CNAME record, which should exist (otherwise we should @@ -711,9 +830,14 @@ for (i = 0; i < 10; i++) 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, 256); + cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, sizeof(data)); if (datalen < 0) return DNS_FAIL; name = data; + + if (!dns_is_secure(dnsa)) + secure_so_far = FALSE; + + 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 @@ -751,7 +875,7 @@ Returns: DNS_SUCCEED successful lookup */ int -dns_special_lookup(dns_answer *dnsa, uschar *name, int type, +dns_special_lookup(dns_answer *dnsa, const uschar *name, int type, uschar **fully_qualified_name) { if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name); @@ -765,7 +889,7 @@ root servers. */ if (type == T_ZNS) { - uschar *d = name; + const uschar *d = name; while (d != 0) { int rc = dns_lookup(dnsa, d, T_NS, fully_qualified_name); @@ -798,7 +922,7 @@ if (type == T_CSA) rc = dns_lookup(dnsa, srvname, T_SRV, NULL); if (rc == DNS_SUCCEED || rc == DNS_AGAIN) { - if (rc == DNS_SUCCEED) *fully_qualified_name = name; + if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name); return rc; } @@ -906,7 +1030,7 @@ if (type == T_CSA) /* Extract the numerical SRV fields (p is incremented) */ p = rr->data; GETSHORT(priority, p); - GETSHORT(weight, p); + GETSHORT(weight, p); weight = weight; /* compiler quietening */ GETSHORT(port, p); /* Check the CSA version number */ @@ -1148,4 +1272,6 @@ else return yield; } +/* vi: aw ai sw=2 +*/ /* End of dns.c */