resolved: on negative NODATA replies, properly deal with empty non-terminals
empty non-terminals generally lack NSEC RRs, which means we can deduce their existance only from the fact that there are other RRs that contain them in their suffix. Specifically, the NSEC proof for NODATA on ENTs works by sending the NSEC whose next name is a suffix of the queried name to the client. Use this information properly.
This commit is contained in:
parent
96bb76734d
commit
b9282bc128
|
@ -1617,10 +1617,47 @@ found_closest_encloser:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
|
||||||
|
const char *nn, *common_suffix;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(rr);
|
||||||
|
assert(rr->key->type == DNS_TYPE_NSEC);
|
||||||
|
|
||||||
|
/* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT)
|
||||||
|
*
|
||||||
|
* A couple of examples:
|
||||||
|
*
|
||||||
|
* NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT
|
||||||
|
* NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs
|
||||||
|
* NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* First, determine parent of next domain. */
|
||||||
|
nn = rr->nsec.next_domain_name;
|
||||||
|
r = dns_name_parent(&nn);
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* If the name we just determined is not equal or child of the name we are interested in, then we can't say
|
||||||
|
* anything at all. */
|
||||||
|
r = dns_name_endswith(nn, name);
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
|
||||||
|
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return dns_name_endswith(name, common_suffix);
|
||||||
|
}
|
||||||
|
|
||||||
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
|
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
|
||||||
DnsResourceRecord *rr;
|
|
||||||
bool have_nsec3 = false;
|
bool have_nsec3 = false;
|
||||||
|
DnsResourceRecord *rr;
|
||||||
DnsAnswerFlags flags;
|
DnsAnswerFlags flags;
|
||||||
|
const char *name;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(key);
|
assert(key);
|
||||||
|
@ -1628,6 +1665,8 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
|
||||||
|
|
||||||
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
|
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
|
||||||
|
|
||||||
|
name = DNS_RESOURCE_KEY_NAME(key);
|
||||||
|
|
||||||
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
|
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
|
||||||
|
|
||||||
if (rr->key->class != key->class)
|
if (rr->key->class != key->class)
|
||||||
|
@ -1637,7 +1676,7 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
|
||||||
|
|
||||||
case DNS_TYPE_NSEC:
|
case DNS_TYPE_NSEC:
|
||||||
|
|
||||||
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
|
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
if (r > 0) {
|
if (r > 0) {
|
||||||
|
@ -1669,11 +1708,13 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
|
/* Check if the name we are looking for is an empty non-terminal within the owner or next name
|
||||||
|
* of the NSEC RR. */
|
||||||
|
r = dnssec_nsec_in_path(rr, name);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
if (r > 0) {
|
if (r > 0) {
|
||||||
*result = DNSSEC_NSEC_NXDOMAIN;
|
*result = DNSSEC_NSEC_NODATA;
|
||||||
|
|
||||||
if (authenticated)
|
if (authenticated)
|
||||||
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||||
|
@ -1682,7 +1723,19 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r > 0)
|
||||||
|
*result = DNSSEC_NSEC_NXDOMAIN;
|
||||||
|
|
||||||
|
if (authenticated)
|
||||||
|
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
|
||||||
|
if (ttl)
|
||||||
|
*ttl = rr->ttl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
case DNS_TYPE_NSEC3:
|
case DNS_TYPE_NSEC3:
|
||||||
have_nsec3 = true;
|
have_nsec3 = true;
|
||||||
|
|
|
@ -1157,22 +1157,20 @@ finish:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
|
static int dns_name_build_suffix_table(const char *name, const char*table[]) {
|
||||||
const char* labels[DNS_N_LABELS_MAX+1];
|
|
||||||
unsigned n = 0;
|
|
||||||
const char *p;
|
const char *p;
|
||||||
|
unsigned n = 0;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(name);
|
assert(name);
|
||||||
assert(ret);
|
assert(table);
|
||||||
|
|
||||||
p = name;
|
p = name;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (n > DNS_N_LABELS_MAX)
|
if (n > DNS_N_LABELS_MAX)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
labels[n] = p;
|
table[n] = p;
|
||||||
|
|
||||||
r = dns_name_parent(&p);
|
r = dns_name_parent(&p);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
@ -1182,7 +1180,21 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n < n_labels)
|
return (int) n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
|
||||||
|
const char* labels[DNS_N_LABELS_MAX+1];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
assert(name);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
n = dns_name_build_suffix_table(name, labels);
|
||||||
|
if (n < 0)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
if ((unsigned) n < n_labels)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
*ret = labels[n - n_labels];
|
*ret = labels[n - n_labels];
|
||||||
|
@ -1245,3 +1257,49 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
|
||||||
|
|
||||||
return dns_name_equal(a, b);
|
return dns_name_equal(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
|
||||||
|
const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
|
||||||
|
int n = 0, m = 0, k = 0, r, q;
|
||||||
|
|
||||||
|
assert(a);
|
||||||
|
assert(b);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
/* Determines the common suffix of domain names a and b */
|
||||||
|
|
||||||
|
n = dns_name_build_suffix_table(a, a_labels);
|
||||||
|
if (n < 0)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
m = dns_name_build_suffix_table(b, b_labels);
|
||||||
|
if (m < 0)
|
||||||
|
return m;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
|
||||||
|
const char *x, *y;
|
||||||
|
|
||||||
|
if (k >= n || k >= m) {
|
||||||
|
*ret = a_labels[n - k];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = a_labels[n - 1 - k];
|
||||||
|
r = dns_label_unescape_undo_idna(&x, la, sizeof(la));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
y = b_labels[m - 1 - k];
|
||||||
|
q = dns_label_unescape_undo_idna(&y, lb, sizeof(lb));
|
||||||
|
if (q < 0)
|
||||||
|
return q;
|
||||||
|
|
||||||
|
if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
|
||||||
|
*ret = a_labels[n - k];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,3 +106,5 @@ int dns_name_count_labels(const char *name);
|
||||||
|
|
||||||
int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
|
int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
|
||||||
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
|
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
|
||||||
|
|
||||||
|
int dns_name_common_suffix(const char *a, const char *b, const char **ret);
|
||||||
|
|
|
@ -578,6 +578,26 @@ static void test_dns_name_compare_func(void) {
|
||||||
assert_se(dns_name_compare_func("de.", "heise.de") != 0);
|
assert_se(dns_name_compare_func("de.", "heise.de") != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_dns_name_common_suffix_one(const char *a, const char *b, const char *result) {
|
||||||
|
const char *c;
|
||||||
|
|
||||||
|
assert_se(dns_name_common_suffix(a, b, &c) >= 0);
|
||||||
|
assert_se(streq(c, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_dns_name_common_suffix(void) {
|
||||||
|
test_dns_name_common_suffix_one("", "", "");
|
||||||
|
test_dns_name_common_suffix_one("foo", "", "");
|
||||||
|
test_dns_name_common_suffix_one("", "foo", "");
|
||||||
|
test_dns_name_common_suffix_one("foo", "bar", "");
|
||||||
|
test_dns_name_common_suffix_one("bar", "foo", "");
|
||||||
|
test_dns_name_common_suffix_one("foo", "foo", "foo");
|
||||||
|
test_dns_name_common_suffix_one("quux.foo", "foo", "foo");
|
||||||
|
test_dns_name_common_suffix_one("foo", "quux.foo", "foo");
|
||||||
|
test_dns_name_common_suffix_one("this.is.a.short.sentence", "this.is.another.short.sentence", "short.sentence");
|
||||||
|
test_dns_name_common_suffix_one("FOO.BAR", "tEST.bAR", "BAR");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
test_dns_label_unescape();
|
test_dns_label_unescape();
|
||||||
|
@ -603,6 +623,7 @@ int main(int argc, char *argv[]) {
|
||||||
test_dns_name_count_labels();
|
test_dns_name_count_labels();
|
||||||
test_dns_name_equal_skip();
|
test_dns_name_equal_skip();
|
||||||
test_dns_name_compare_func();
|
test_dns_name_compare_func();
|
||||||
|
test_dns_name_common_suffix();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue