X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fdns.c;h=e13aa74e24b17d880885bfd5e5cdad393df628d4;hp=0f0b435de288b9f7ca8bad0421086387f88daafc;hb=dce58c04af4439fec7269f83886e22b503756a8f;hpb=75c121f07a85b4029458f11b113a4655114af126 diff --git a/src/src/dns.c b/src/src/dns.c index 0f0b435de..e13aa74e2 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -10,7 +10,6 @@ #include "exim.h" - /************************************************* * Fake DNS resolver * *************************************************/ @@ -40,7 +39,7 @@ fakens_search(const uschar *domain, int type, uschar *answerptr, int size) { int len = Ustrlen(domain); int asize = size; /* Locally modified */ -uschar name[256]; +uschar * name; uschar utilname[256]; uschar *aptr = answerptr; /* Locally modified */ struct stat statbuf; @@ -48,8 +47,7 @@ struct stat statbuf; /* Remove terminating dot. */ if (domain[len - 1] == '.') len--; -Ustrncpy(name, domain, len); -name[len] = 0; +name = string_copyn(domain, len); /* Look for the fakens utility, and if it exists, call it. */ @@ -62,7 +60,8 @@ if (stat(CS utilname, &statbuf) >= 0) 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)); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n", + name, dns_text_type(type)); argv[0] = utilname; argv[1] = config_main_directory; @@ -70,7 +69,7 @@ if (stat(CS utilname, &statbuf) >= 0) argv[3] = dns_text_type(type); argv[4] = NULL; - pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE); + pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE, US"fakens-search"); if (pid < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to run fakens: %s", strerror(errno)); @@ -223,16 +222,15 @@ a name that can be used to look up PTR records. Arguments: string the IP address as a string - buffer a suitable buffer, long enough to hold the result -Returns: nothing +Returns: an allocated string */ -void -dns_build_reverse(const uschar *string, uschar *buffer) +uschar * +dns_build_reverse(const uschar * string) { -const uschar *p = string + Ustrlen(string); -uschar *pp = buffer; +const uschar * p = string + Ustrlen(string); +gstring * g = NULL; /* Handle IPv4 address */ @@ -240,17 +238,15 @@ uschar *pp = buffer; if (Ustrchr(string, ':') == NULL) #endif { - int i; - for (i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { - const uschar *ppp = p; + const uschar * ppp = p; while (ppp > string && ppp[-1] != '.') ppp--; - Ustrncpy(pp, ppp, p - ppp); - pp += p - ppp; - *pp++ = '.'; + g = string_catn(g, ppp, p - ppp); + g = string_catn(g, US".", 1); p = ppp - 1; } - Ustrcpy(pp, "in-addr.arpa"); + g = string_catn(g, US"in-addr.arpa", 12); } /* Handle IPv6 address; convert to binary so as to fill out any @@ -259,21 +255,19 @@ abbreviation in the textual form. */ #if HAVE_IPV6 else { - int i; int v6[4]; + + g = string_get_tainted(32, is_tainted(string)); (void)host_aton(string, v6); /* The original specification for IPv6 reverse lookup was to invert each nibble, and look in the ip6.int domain. The domain was subsequently changed to ip6.arpa. */ - for (i = 3; i >= 0; i--) - { - int j; - for (j = 0; j < 32; j += 4) - pp += sprintf(CS pp, "%x.", (v6[i] >> j) & 15); - } - Ustrcpy(pp, "ip6.arpa."); + for (int i = 3; i >= 0; i--) + for (int j = 0; j < 32; j += 4) + g = string_fmt_append(g, "%x.", (v6[i] >> j) & 15); + g = string_catn(g, US"ip6.arpa.", 9); /* Another way of doing IPv6 reverse lookups was proposed in conjunction with A6 records. However, it fell out of favour when they did. The @@ -287,16 +281,17 @@ else Ustrcpy(pp, "\\[x"); pp += 3; - for (i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { sprintf(pp, "%08X", v6[i]); pp += 8; } - Ustrcpy(pp, "].ip6.arpa."); + Ustrcpy(pp, US"].ip6.arpa."); **************************************************/ } #endif +return string_from_gstring(g); } @@ -441,6 +436,7 @@ 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. */ +TRACE debug_printf("%s: return %s\n", __FUNCTION__, dns_text_type(dnss->srr.type)); return &dnss->srr; null_return: @@ -467,11 +463,10 @@ 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); + for (dns_record * 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); @@ -505,13 +500,15 @@ const HEADER * h = (const HEADER *) dnsa->answer; const uschar * auth_name; const uschar * trusted; +if (dnsa->answerlen < 0) return FALSE; +/* Beware that newer versions of glibc on Linux will filter out the ad bit +unless their shiny new RES_TRUSTAD bit is set for the resolver. */ 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 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 @@ -547,12 +544,12 @@ h->aa = h->ad = 0; ************************************************/ BOOL -dns_is_aa(const dns_answer *dnsa) +dns_is_aa(const dns_answer * dnsa) { #ifdef DISABLE_DNSSEC return FALSE; #else -return ((const HEADER*)dnsa->answer)->aa; +return dnsa->answerlen >= 0 && ((const HEADER *)dnsa->answer)->aa; #endif } @@ -600,6 +597,10 @@ static void dns_fail_tag(uschar * buf, const uschar * name, int dns_type) { res_state resp = os_get_dns_resolver_res(); + +/*XX buf needs to be 255 +1 + (max(typetext) == 5) +1 + max(chars_for_long-max) +1 +We truncate the name here for safety... could use a dynamic string. */ + sprintf(CS buf, "%.255s-%s-%lx", name, dns_text_type(dns_type), (unsigned long) resp->options); } @@ -613,21 +614,155 @@ caching. Arguments: name the domain name type the lookup type + expiry time TTL expires, or zero for unlimited rc the return code Returns: the return code */ +/* we need: 255 +1 + (max(typetext) == 5) +1 + max(chars_for_long-max) +1 */ +#define DNS_FAILTAG_MAX 290 +#define DNS_FAILNODE_SIZE \ + (sizeof(expiring_data) + sizeof(tree_node) + DNS_FAILTAG_MAX) + static int -dns_return(const uschar * name, int type, int rc) +dns_fail_return(const uschar * name, int type, time_t expiry, int rc) { -tree_node *node = store_get_perm(sizeof(tree_node) + 290); -dns_fail_tag(node->name, name, type); -node->data.val = rc; -(void)tree_insertnode(&tree_dns_fails, node); +uschar node_name[DNS_FAILTAG_MAX]; +tree_node * previous, * new; +expiring_data * e; + +dns_fail_tag(node_name, name, type); +if ((previous = tree_search(tree_dns_fails, node_name))) + e = previous->data.ptr; +else + { + e = store_get_perm(DNS_FAILNODE_SIZE, is_tainted(name)); + new = (void *)(e+1); + dns_fail_tag(new->name, name, type); + new->data.ptr = e; + (void)tree_insertnode(&tree_dns_fails, new); + } + +DEBUG(D_dns) debug_printf(" %s neg-cache entry for %s, ttl %d\n", + previous ? "update" : "writing", + node_name, expiry ? (int)(expiry - time(NULL)) : -1); +e->expiry = expiry; +e->data.val = rc; return rc; } + +/* Return the cached result of a known-bad lookup, or -1. +*/ +static int +dns_fail_cache_hit(const uschar * name, int type) +{ +uschar node_name[DNS_FAILTAG_MAX]; +tree_node * previous; +expiring_data * e; +int val, rc; + +dns_fail_tag(node_name, name, type); +if (!(previous = tree_search(tree_dns_fails, node_name))) + return -1; + +e = previous->data.ptr; +val = e->data.val; +rc = e->expiry && e->expiry <= time(NULL) ? -1 : val; + +DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: %scached value %s%s\n", + name, dns_text_type(type), + rc == -1 ? "" : "using ", + val == DNS_NOMATCH ? "DNS_NOMATCH" : + val == DNS_NODATA ? "DNS_NODATA" : + val == DNS_AGAIN ? "DNS_AGAIN" : + val == DNS_FAIL ? "DNS_FAIL" : "??", + rc == -1 ? " past valid time" : ""); + +return rc; +} + + + +/* This is really gross. The successful return value from res_search() is +the packet length, which is stored in dnsa->answerlen. If we get a +negative DNS reply then res_search() returns -1, which causes the bounds +checks for name decompression to fail when it is treated as a packet +length, which in turn causes the authority search to fail. The correct +packet length has been lost inside libresolv, so we have to guess a +replacement value. (The only way to fix this properly would be to +re-implement res_search() and res_query() so that they don't muddle their +success and packet length return values.) For added safety we only reset +the packet length if the packet header looks plausible. */ + +static void +fake_dnsa_len_for_fail(dns_answer * dnsa, int type) +{ +const HEADER * h = (const HEADER *)dnsa->answer; + +if ( h->qr == 1 /* a response */ + && h->opcode == QUERY + && h->tc == 0 /* nmessage not truncated */ + && (h->rcode == NOERROR || h->rcode == NXDOMAIN) + && ( ntohs(h->qdcount) == 1 /* one question record */ + || f.running_in_test_harness) + && ntohs(h->ancount) == 0 /* no answer records */ + && ntohs(h->nscount) >= 1) /* authority records */ + { + DEBUG(D_dns) debug_printf("faking res_search(%s) response length as %d\n", + dns_text_type(type), (int)sizeof(dnsa->answer)); + dnsa->answerlen = sizeof(dnsa->answer); + } +} + + +/* Return the TTL suitable for an NXDOMAIN result, which is given +in the SOA. We hope that one was returned in the lookup, and do not +bother doing a separate lookup; if not found return a forever TTL. +*/ + +time_t +dns_expire_from_soa(dns_answer * dnsa, int type) +{ +dns_scan dnss; + +fake_dnsa_len_for_fail(dnsa, type); + +for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); + rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_SOA) + { + const uschar * p = rr->data; + uschar discard_buf[256]; + int len; + unsigned long ttl; + + /* Skip the mname & rname strings */ + + if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, + p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) + break; + p += len; + if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, + p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0) + break; + p += len; + + /* Skip the SOA serial, refresh, retry & expire. Grab the TTL */ + + if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ) + break; + p += 4 * INT32SZ; + GETLONG(ttl, p); + + return time(NULL) + ttl; + } +DEBUG(D_dns) debug_printf("DNS: no SOA record found for neg-TTL\n"); +return 0; +} + + /************************************************* * Do basic DNS lookup * *************************************************/ @@ -658,31 +793,23 @@ Returns: DNS_SUCCEED successful lookup */ int -dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type) +dns_basic_lookup(dns_answer * dnsa, const uschar * name, int type) { +int rc; #ifndef STAND_ALONE -int rc = -1; -const uschar *save_domain; +const uschar * save_domain; #endif -tree_node *previous; -uschar node_name[290]; - /* DNS lookup failures of any kind are cached in a tree. This is mainly so that a timeout on one domain doesn't happen time and time again for messages that have many addresses in the same domain. We rely on the resolver and name server -caching for successful lookups. */ +caching for successful lookups. +*/ -dns_fail_tag(node_name, name, type); -if ((previous = tree_search(tree_dns_fails, node_name))) +if ((rc = dns_fail_cache_hit(name, type)) > 0) { - DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n", - name, dns_text_type(type), - previous->data.val == DNS_NOMATCH ? "DNS_NOMATCH" : - previous->data.val == DNS_NODATA ? "DNS_NODATA" : - previous->data.val == DNS_AGAIN ? "DNS_AGAIN" : - previous->data.val == DNS_FAIL ? "DNS_FAIL" : "??"); - return previous->data.val; + dnsa->answerlen = -1; + return rc; } #ifdef SUPPORT_I18N @@ -713,30 +840,17 @@ regex has substrings that are used - the default uses a conditional. This test is omitted for PTR records. These occur only in calls from the dnsdb lookup, which constructs the names itself, so they should be OK. Besides, -bitstring labels don't conform to normal name syntax. (But the aren't used any -more.) - -For SRV records, we omit the initial _smtp._tcp. components at the start. */ +bitstring labels don't conform to normal name syntax. (But they aren't used any +more.) */ #ifndef STAND_ALONE /* Omit this for stand-alone tests */ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT) { - const uschar *checkname = name; int ovector[3*(EXPAND_MAXN+1)]; dns_pattern_init(); - - /* 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 || type == T_TLSA) - { - while (*checkname++ != '.'); - while (*checkname++ != '.'); - } - - if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname), + if (pcre_exec(regex_check_dns_names, NULL, CCS name, Ustrlen(name), 0, PCRE_EOPT, ovector, nelem(ovector)) < 0) { DEBUG(D_dns) @@ -765,6 +879,7 @@ if ((type == T_A || type == T_AAAA) && string_is_ip_address(name, NULL) != 0) (res_search), we call fakens_search(), which recognizes certain special domains, and interfaces to a fake nameserver for certain special zones. */ +h_errno = 0; dnsa->answerlen = f.running_in_test_harness ? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer)) : res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer)); @@ -782,7 +897,7 @@ 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); + return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH); case TRY_AGAIN: DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", @@ -798,30 +913,30 @@ if (dnsa->answerlen < 0) switch (h_errno) if (rc != OK) { DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); - return dns_return(name, type, DNS_AGAIN); + return dns_fail_return(name, type, 0, 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); + return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH); #else /* For stand-alone tests */ - return dns_return(name, type, DNS_AGAIN); + return dns_fail_return(name, type, 0, 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); + return dns_fail_return(name, type, 0, 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); + return dns_fail_return(name, type, dns_expire_from_soa(dnsa, 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); + return dns_fail_return(name, type, 0, DNS_FAIL); } DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n", @@ -873,7 +988,6 @@ int dns_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { -int i; const uschar *orig_name = name; BOOL secure_so_far = TRUE; @@ -884,10 +998,10 @@ resolvers hiding behind a config variable. Loop to follow CNAME chains so far, but no further... The testsuite tests the latter case, mostly assuming that the former will work. */ -for (i = 0; i <= dns_cname_loops; i++) +for (int i = 0; i <= dns_cname_loops; i++) { uschar * data; - dns_record *rr, cname_rr, type_rr; + dns_record cname_rr, type_rr; dns_scan dnss; int rc; @@ -903,7 +1017,7 @@ for (i = 0; i <= dns_cname_loops; i++) area in the dnsa block. */ cname_rr.data = type_rr.data = NULL; - for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type) { @@ -950,7 +1064,8 @@ for (i = 0; i <= dns_cname_loops; i++) if (!cname_rr.data) return DNS_FAIL; - data = store_get(256); + /* DNS data comes from the outside, hence tainted */ + data = store_get(256, TRUE); if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0) return DNS_FAIL; @@ -1089,23 +1204,7 @@ switch (type) if (rc == DNS_NOMATCH) { - /* This is really gross. The successful return value from res_search() is - the packet length, which is stored in dnsa->answerlen. If we get a - negative DNS reply then res_search() returns -1, which causes the bounds - checks for name decompression to fail when it is treated as a packet - length, which in turn causes the authority search to fail. The correct - packet length has been lost inside libresolv, so we have to guess a - replacement value. (The only way to fix this properly would be to - re-implement res_search() and res_query() so that they don't muddle their - success and packet length return values.) For added safety we only reset - the packet length if the packet header looks plausible. */ - - 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 - && ntohs(h->nscount) >= 1) - dnsa->answerlen = sizeof(dnsa->answer); + fake_dnsa_len_for_fail(dnsa, T_CSA); for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) @@ -1204,7 +1303,8 @@ if (rr->type == T_A) uschar *p = US rr->data; if (p + 4 <= dnsa_lim) { - yield = store_get(sizeof(dns_address) + 20); + /* the IP is not regarded as tainted */ + yield = store_get(sizeof(dns_address) + 20, FALSE); (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); yield->next = NULL; } @@ -1217,9 +1317,8 @@ 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); + for (int i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i]; + yield = store_get(sizeof(dns_address) + 50, FALSE); inet_ntop(AF_INET6, &in6, CS yield->address, 50); yield->next = NULL; }