* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
-
/*************************************************
* Fake DNS resolver *
*************************************************/
{
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;
/* 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. */
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;
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));
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 */
{
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
else
{
int v6[4];
+
+ g = string_get_tainted(32, string);
(void)host_aton(string, v6);
/* The original specification for IPv6 reverse lookup was to invert each
for (int i = 3; i >= 0; i--)
for (int j = 0; j < 32; j += 4)
- pp += sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
- Ustrcpy(pp, "ip6.arpa.");
+ 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
sprintf(pp, "%08X", v6[i]);
pp += 8;
}
- Ustrcpy(pp, "].ip6.arpa.");
+ Ustrcpy(pp, US"].ip6.arpa.");
**************************************************/
}
#endif
+return string_from_gstring(g);
}
#ifdef rr_trace
# define TRACE DEBUG(D_dns)
#else
-trace = trace;
# define TRACE if (FALSE)
#endif
/* 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:
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
|| !(trusted = expand_string(dns_trust_aa))
|| !*trusted
|| !(auth_name = dns_extract_auth_name(dnsa))
- || OK != match_isinlist(auth_name, &trusted, 0, NULL, NULL,
+ || OK != match_isinlist(auth_name, &trusted, 0, &domainlist_anchor, NULL,
MCL_DOMAIN, TRUE, NULL)
)
return FALSE;
************************************************/
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
}
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);
}
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_fail_return(const uschar * name, int type, time_t expiry, int rc)
+{
+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, 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_return(const uschar * name, int type, int rc)
+dns_fail_cache_hit(const uschar * name, int type)
{
-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;
+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 ",
+ dns_rc_names[val],
+ 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.
+
+Return TRUE iff it seemed ok */
+
+static BOOL
+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 TRUE;
+ }
+DEBUG(D_dns) debug_printf("DNS: couldn't fake dnsa len\n");
+/* Maybe we should just do a second lookup for an SOA? */
+return FALSE;
+}
+
+
+/* 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;
+
+if (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 *
*************************************************/
*/
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
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.
-The check has been seen to bite on the destination of a SRV lookup that
-initiall hit a CNAME, for which the next name had only two components.
-RFC2782 makes no mention of the possibiility of CNAMES, but the Wikipedia
-article on SRV says they are not a valid configuration. */
+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 && *checkname++ != '.') ;
- while (*checkname && *checkname++ != '.') ;
- }
-
- if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
- 0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
+ if (!regex_match(regex_check_dns_names, name, -1, NULL))
{
DEBUG(D_dns)
debug_printf("DNS name syntax check failed: %s (%s)\n", name,
(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));
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",
#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);
+ rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0,
+ &domainlist_anchor, 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);
+ 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",
if (!cname_rr.data)
return DNS_FAIL;
- data = store_get(256);
+ /* DNS data comes from the outside, hence tainted */
+ data = store_get(256, GET_TAINTED);
if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0)
return DNS_FAIL;
case T_CSA:
{
uschar *srvname, *namesuff, *tld;
- int priority, weight, port;
+ int priority, dummy_weight, port;
int limit, rc, i;
BOOL ipv6;
dns_record *rr;
If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */
- 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);
-
- for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
- rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
- )
- if (rr->type != T_SOA) continue;
- else if (strcmpic(rr->name, US"") == 0 ||
- strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
- else break;
- }
+ if (rc == DNS_NOMATCH) return DNS_NOMATCH;
for (i = 0; i < limit; i++)
{
/* Extract the numerical SRV fields (p is incremented) */
GETSHORT(priority, p);
- GETSHORT(weight, p); weight = weight; /* compiler quietening */
+ GETSHORT(dummy_weight, p);
GETSHORT(port, p);
/* Check the CSA version number */
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, GET_UNTAINTED);
(void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
yield->next = NULL;
}
{
struct in6_addr in6;
for (int i = 0; i < 16; i++) in6.s6_addr[i] = rr->data[i];
- yield = store_get(sizeof(dns_address) + 50);
+ yield = store_get(sizeof(dns_address) + 50, GET_UNTAINTED);
inet_ntop(AF_INET6, &in6, CS yield->address, 50);
yield->next = NULL;
}
{
if (check_dns_names_pattern[0] != 0 && !regex_check_dns_names)
regex_check_dns_names =
- regex_must_compile(check_dns_names_pattern, FALSE, TRUE);
+ regex_must_compile(check_dns_names_pattern, MCS_NOFLAGS, TRUE);
}
/* vi: aw ai sw=2