diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1ee4aa5b36..69ed7afaf7 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1617,10 +1617,47 @@ found_closest_encloser: 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) { - DnsResourceRecord *rr; bool have_nsec3 = false; + DnsResourceRecord *rr; DnsAnswerFlags flags; + const char *name; int r; 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. */ + name = DNS_RESOURCE_KEY_NAME(key); + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { if (rr->key->class != key->class) @@ -1637,7 +1676,7 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r 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) return r; if (r > 0) { @@ -1669,11 +1708,13 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r 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) return r; if (r > 0) { - *result = DNSSEC_NSEC_NXDOMAIN; + *result = DNSSEC_NSEC_NODATA; if (authenticated) *authenticated = flags & DNS_ANSWER_AUTHENTICATED; @@ -1682,7 +1723,19 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r 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: have_nsec3 = true; diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index ee0108715d..04624734dc 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -1157,22 +1157,20 @@ finish: return 0; } -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { - const char* labels[DNS_N_LABELS_MAX+1]; - unsigned n = 0; +static int dns_name_build_suffix_table(const char *name, const char*table[]) { const char *p; + unsigned n = 0; int r; assert(name); - assert(ret); + assert(table); p = name; for (;;) { if (n > DNS_N_LABELS_MAX) return -EINVAL; - labels[n] = p; - + table[n] = p; r = dns_name_parent(&p); if (r < 0) return r; @@ -1182,7 +1180,21 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { 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; *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); } + +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++; + } +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index a679d40958..5f9542ef98 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -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_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); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index fe3ae45349..987f1fc887 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -578,6 +578,26 @@ static void test_dns_name_compare_func(void) { 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[]) { test_dns_label_unescape(); @@ -603,6 +623,7 @@ int main(int argc, char *argv[]) { test_dns_name_count_labels(); test_dns_name_equal_skip(); test_dns_name_compare_func(); + test_dns_name_common_suffix(); return 0; }