* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SPDX-License-Identifier: GPL-2.0-or-later */
int infd, outfd, rc;
uschar *argv[5];
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n",
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) using fakens\n",
name, dns_text_type(type));
argv[0] = utilname;
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");
+ DEBUG(D_dns) debug_printf_indent("fakens returned PASS_ON\n");
}
}
else
{
- DEBUG(D_dns) debug_printf("fakens (%s) not found\n", utilname);
+ DEBUG(D_dns) debug_printf_indent("fakens (%s) not found\n", utilname);
}
/* 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_indent("passing %s on to res_search()\n", domain);
return res_search(CS domain, C_IN, type, answerptr, size);
}
else
resp->options &= ~RES_USE_EDNS0;
DEBUG(D_resolver)
- debug_printf("Coerced resolver EDNS0 support %s.\n",
+ debug_printf_indent("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",
+ debug_printf_indent("Unable to %sset EDNS0 without resolver support.\n",
dns_use_edns0 ? "" : "un");
#endif
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");
+ debug_printf_indent("CONFLICT: dns_use_edns0 forced false, dns_dnssec_ok forced true, ignoring latter!\n");
}
else
{
resp->options |= RES_USE_DNSSEC;
else
resp->options &= ~RES_USE_DNSSEC;
- DEBUG(D_resolver) debug_printf("Coerced resolver DNSSEC support %s.\n",
+ DEBUG(D_resolver) debug_printf_indent("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",
+ debug_printf_indent("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");
+ debug_printf_indent("Unable to set DNSSEC without resolver support.\n");
# endif
#endif /* DISABLE_DNSSEC */
+/* Check a pointer for being past the end of a dns answer.
+Exactly one past the end is defined as ok.
+Return TRUE iff bad.
+*/
+static BOOL
+dnsa_bad_ptr(const dns_answer * dnsa, const uschar * ptr)
+{
+return ptr > dnsa->answer + dnsa->answerlen;
+}
+
/* 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;
+return dnsa_bad_ptr(dnsa, dnss->aptr += delta);
}
/*************************************************
if (reset != RESET_NEXT)
{
dnss->rrcount = ntohs(h->qdcount);
- TRACE debug_printf("%s: reset (Q rrcount %d)\n", __FUNCTION__, dnss->rrcount);
+ TRACE debug_printf_indent("%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 */
/* Get the number of answer records. */
dnss->rrcount = ntohs(h->ancount);
- TRACE debug_printf("%s: reset (A rrcount %d)\n", __FUNCTION__, dnss->rrcount);
+ TRACE debug_printf_indent("%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
if (reset == RESET_ADDITIONAL)
{
- TRACE debug_printf("%s: additional\n", __FUNCTION__);
+ TRACE debug_printf_indent("%s: additional\n", __FUNCTION__);
dnss->rrcount += ntohs(h->nscount);
- TRACE debug_printf("%s: reset (NS rrcount %d)\n", __FUNCTION__, dnss->rrcount);
+ TRACE debug_printf_indent("%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__);
+ debug_printf_indent("%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) goto null_return;
+
/* skip name, type, class & TTL */
TRACE trace = "A-hdr";
if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return;
+
+ if (dnsa_bad_ptr(dnsa, dnss->aptr + sizeof(uint16_t))) goto null_return;
GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
- /* skip over it */
+
+ /* skip over it, checking for a bogus size */
TRACE trace = "A-skip";
if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return;
}
dnss->rrcount = reset == RESET_AUTHORITY
? ntohs(h->nscount) : ntohs(h->arcount);
- TRACE debug_printf("%s: reset (%s rrcount %d)\n", __FUNCTION__,
+ TRACE debug_printf_indent("%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);
+ TRACE debug_printf_indent("%s: %d RRs to read\n", __FUNCTION__, dnss->rrcount);
}
else
- TRACE debug_printf("%s: next (%d left)\n", __FUNCTION__, dnss->rrcount);
+ TRACE debug_printf_indent("%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 (namelen < 0) goto null_return;
/* Move the pointer past the name and fill in the rest of the data structure
-from the following bytes. */
+from the following bytes. We seem to be assuming here that the RR blob passed
+to us by the resolver library is the same as that defined for an RR by RFC 1035
+section 3.2.1 */
TRACE trace = "R-name";
if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
-GETSHORT(dnss->srr.type, dnss->aptr); /* Record type */
+/* Check space for type, class, TTL & data-size-word */
+if (dnsa_bad_ptr(dnsa, dnss->aptr + 3 * sizeof(uint16_t) + sizeof(uint32_t)))
+ 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 */
+(void) dnss_inc_aptr(dnsa, dnss, sizeof(uint16_t)); /* skip class */
-/* Unchecked increment ok here since no further access on this iteration;
-will be checked on next at "R-name". */
+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 */
+/* skip over it, checking for a bogus size */
+if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size))
+ goto null_return;
/* 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));
+TRACE debug_printf_indent("%s: return %s\n", __FUNCTION__, dns_text_type(dnss->srr.type));
return &dnss->srr;
null_return:
- TRACE debug_printf("%s: terminate (%d RRs left). Last op: %s; errno %d %s\n",
+ TRACE debug_printf_indent("%s: terminate (%d RRs left). Last op: %s; errno %d %s\n",
__FUNCTION__, dnss->rrcount, trace, errno, strerror(errno));
dnss->rrcount = 0;
return NULL;
{
#ifdef DISABLE_DNSSEC
DEBUG(D_dns)
- debug_printf("DNSSEC support disabled at build-time; dns_is_secure() false\n");
+ debug_printf_indent("DNSSEC support disabled at build-time; dns_is_secure() false\n");
return FALSE;
#else
const HEADER * h = (const HEADER *) dnsa->answer;
)
return FALSE;
-DEBUG(D_dns) debug_printf("DNS faked the AD bit "
+DEBUG(D_dns) debug_printf_indent("DNS faked the AD bit "
"(got AA and matched with dns_trust_aa (%s in %s))\n",
auth_name, dns_trust_aa);
(void)tree_insertnode(&tree_dns_fails, new);
}
-DEBUG(D_dns) debug_printf(" %s neg-cache entry for %s, ttl %d\n",
+DEBUG(D_dns) debug_printf_indent(" %s neg-cache entry for %s, ttl %d\n",
previous ? "update" : "writing",
node_name, expiry ? (int)(expiry - time(NULL)) : -1);
e->expiry = expiry;
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",
+DEBUG(D_dns) debug_printf_indent("DNS lookup of %.255s (%s): %scached value %s%s\n",
name, dns_text_type(type),
rc == -1 ? "" : "using ",
dns_rc_names[val],
&& 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",
+ DEBUG(D_dns) debug_printf_indent("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");
+DEBUG(D_dns) debug_printf_indent("DNS: couldn't fake dnsa len\n");
/* Maybe we should just do a second lookup for an SOA? */
return FALSE;
}
/* Skip the mname & rname strings */
if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
+ p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0)
break;
p += len;
if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
+ p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0)
break;
p += len;
/* Skip the SOA serial, refresh, retry & expire. Grab the TTL */
- if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ)
+ if (dnsa_bad_ptr(dnsa, p + 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");
+DEBUG(D_dns) debug_printf_indent("DNS: no SOA record found for neg-TTL\n");
return 0;
}
uschar * alabel;
uschar * errstr = NULL;
DEBUG(D_dns) if (string_is_utf8(name))
- debug_printf("convert utf8 '%s' to alabel for for lookup\n", name);
+ debug_printf_indent("convert utf8 '%s' to alabel for for lookup\n", name);
if ((alabel = string_domain_utf8_to_alabel(name, &errstr)), errstr)
{
DEBUG(D_dns)
- debug_printf("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
+ debug_printf_indent("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
errstr);
f.host_find_failed_syntax = TRUE;
return DNS_NOMATCH;
if (!regex_match(regex_check_dns_names, name, -1, NULL))
{
DEBUG(D_dns)
- debug_printf("DNS name syntax check failed: %s (%s)\n", name,
+ debug_printf_indent("DNS name syntax check failed: %s (%s)\n", name,
dns_text_type(type));
f.host_find_failed_syntax = TRUE;
return DNS_NOMATCH;
if (dnsa->answerlen > (int) sizeof(dnsa->answer))
{
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet"
+ DEBUG(D_dns) debug_printf_indent("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"
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
"returning DNS_NOMATCH\n", name, dns_text_type(type));
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",
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave TRY_AGAIN\n",
name, dns_text_type(type));
/* Cut this out for various test programs */
if (rc != OK)
{
- DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
+ DEBUG(D_dns) debug_printf_indent("returning DNS_AGAIN\n");
return dns_fail_return(name, type, 0, DNS_AGAIN);
}
- DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
+ DEBUG(D_dns) debug_printf_indent("%s is in dns_again_means_nonexist: returning "
"DNS_NOMATCH\n", name);
return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH);
#endif
case NO_RECOVERY:
- DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_RECOVERY\n"
"returning DNS_FAIL\n", name, dns_text_type(type));
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"
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_DATA\n"
"returning DNS_NODATA\n", name, dns_text_type(type));
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"
+ DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave unknown DNS error %d\n"
"returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
return dns_fail_return(name, type, 0, DNS_FAIL);
}
-DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
+DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) succeeded\n",
name, dns_text_type(type));
return DNS_SUCCEED;
If fully_qualified_name is not NULL, set it to point to the full name
returned by the resolver, if this is different to what it is given, unless
the returned name starts with "*" as some nameservers seem to be returning
-wildcards in this form. In international mode "different" means "alabel
+wildcards in this form. In international mode "different" means "a-label
forms are different".
Arguments:
*/
int
-dns_lookup(dns_answer *dnsa, const uschar *name, int type,
- const uschar **fully_qualified_name)
+dns_lookup(dns_answer * dnsa, const uschar * name, int type,
+ const uschar ** fully_qualified_name)
{
-const uschar *orig_name = name;
+const uschar * orig_name = name;
BOOL secure_so_far = TRUE;
+int rc = DNS_FAIL;
+const uschar * errstr = NULL;
/* By default, assume the resolver follows CNAME chains (and returns NODATA for
an unterminated one). If it also does that for a CNAME loop, fine; if it returns
uschar * data;
dns_record cname_rr, type_rr;
dns_scan dnss;
- int rc;
/* DNS lookup failures get passed straight back. */
if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED)
- return rc;
+ goto not_good;
/* 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
its not existing. */
if (!cname_rr.data)
- return DNS_FAIL;
+ {
+ errstr = US"no_hit_yet_no_cname";
+ goto not_good;
+ }
/* 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;
+ {
+ errstr = US"bad_expand";
+ goto not_good;
+ }
name = data;
if (!dns_is_secure(dnsa))
secure_so_far = FALSE;
- DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name);
+ DEBUG(D_dns) debug_printf_indent("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
+/* Control reaches here after 10 times round the CNAME loop. Something isn't
right... */
log_write(0, LOG_MAIN, "CNAME loop for %s encountered", orig_name);
-return DNS_FAIL;
+errstr = US"cname_loop";
+
+not_good:
+ {
+#ifndef DISABLE_EVENT
+ const uschar * s = NULL;
+ BOOL save_flag = f.search_find_defer;
+ uschar * save_serr = search_error_message;
+
+ if (!transport_name)
+ s = event_action;
+ else
+ for(transport_instance * tp = transports; tp; tp = tp->drinst.next)
+ if (Ustrcmp(tp->drinst.name, transport_name) == 0)
+ { s = tp->event_action; break; }
+
+ if (s)
+ {
+ if (Ustrchr(name, ':')) /* unlikely, but may as well bugproof */
+ {
+ gstring * g = NULL;
+ while (*name)
+ {
+ if (*name == ':') g = string_catn(g, name, 1);
+ g = string_catn(g, name++, 1);
+ }
+ name = string_from_gstring(g);
+ }
+ event_raise(s, US"dns:fail",
+ string_sprintf("%s:%s:%s",
+ errstr ? errstr : dns_rc_names[rc], name, dns_text_type(type)),
+ NULL);
+ }
+
+ /*XXX what other state could an expansion in the eventhandler mess up? */
+ search_error_message = save_serr;
+ f.search_find_defer = save_flag;
+#endif /*EVENT*/
+ return rc;
+ }
}
case T_SOA:
{
const uschar *d = name;
- while (d != 0)
+ while (d)
{
int rc = dns_lookup(dnsa, d, type, fully_qualified_name);
if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
- while (*d != 0 && *d != '.') d++;
- if (*d++ == 0) break;
+ while (*d && *d != '.') d++;
+ if (!*d++) break;
}
return DNS_NOMATCH;
}
dns_record *rr;
dns_scan dnss;
- DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);
+ DEBUG(D_dns) debug_printf_indent("CSA lookup of %s\n", name);
srvname = string_sprintf("_client._smtp.%s", name);
rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
limit = 3;
}
- DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);
+ DEBUG(D_dns) debug_printf_indent("CSA TLD %s\n", tld);
/* Do not perform the search if the top level or 2nd level domains do not
exist. This is quite common, and when it occurs all the search queries would
if (--namesuff <= name) return DNS_NOMATCH;
while (*namesuff != '.');
- DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);
+ DEBUG(D_dns) debug_printf_indent("CSA parent search at %s\n", namesuff + 1);
srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
const uschar * p = rr->data;
/* Extract the numerical SRV fields (p is incremented) */
+ if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue;
GETSHORT(priority, p);
GETSHORT(dummy_weight, p);
GETSHORT(port, p);