diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 04f64022e0..d963ce6e00 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -20,8 +20,10 @@ ***/ #include "alloc-util.h" +#include "dns-domain.h" #include "resolved-dns-cache.h" #include "resolved-dns-packet.h" +#include "string-util.h" /* Never cache more than 1K entries */ #define CACHE_MAX 1024 @@ -521,25 +523,53 @@ fail: static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceKey *k) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *cname_key = NULL; - DnsCacheItem *i, *j; + DnsCacheItem *i; + const char *n; + int r; assert(c); assert(k); + /* If we hit some OOM error, or suchlike, we don't care too + * much, after all this is just a cache */ + i = hashmap_get(c->by_key, k); - if (i || k->type == DNS_TYPE_CNAME) + if (i || k->type == DNS_TYPE_CNAME || k->type == DNS_TYPE_DNAME) return i; - /* check if we have a CNAME record instead */ + /* Check if we have a CNAME record instead */ cname_key = dns_resource_key_new_cname(k); if (!cname_key) return NULL; - j = hashmap_get(c->by_key, cname_key); - if (j) - return j; + i = hashmap_get(c->by_key, cname_key); + if (i) + return i; - return i; + /* OK, let's look for cached DNAME records. */ + n = DNS_RESOURCE_KEY_NAME(k); + for (;;) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dname_key = NULL; + char label[DNS_LABEL_MAX]; + + if (isempty(n)) + return NULL; + + dname_key = dns_resource_key_new(k->class, DNS_TYPE_DNAME, n); + if (!dname_key) + return NULL; + + i = hashmap_get(c->by_key, dname_key); + if (i) + return i; + + /* Jump one label ahead */ + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return NULL; + } + + return NULL; } int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) { diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 75f2ed6042..9fb3038381 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -207,6 +207,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, assert(cname); assert(ret); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); if (!q) { n = dns_question_new(0); @@ -219,7 +220,22 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, } for (i = 0; i < q->n_keys; i++) { - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), cname->cname.name); + _cleanup_free_ char *destination = NULL; + const char *d; + + if (cname->key->type == DNS_TYPE_CNAME) + d = cname->cname.name; + else { + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(q->keys[i]), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return r; + if (r == 0) + continue; + + d = destination; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), d); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 636e07dea9..9b264900fc 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -57,10 +57,33 @@ DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key) { } DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { + int r; + assert(key); assert(cname); - return dns_resource_key_new(key->class, key->type, cname->cname.name); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (cname->key->type == DNS_TYPE_CNAME) + return dns_resource_key_new(key->class, key->type, cname->cname.name); + else { + DnsResourceKey *k; + char *destination = NULL; + + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return NULL; + if (r == 0) + return dns_resource_key_ref((DnsResourceKey*) key); + + k = dns_resource_key_new_consume(key->class, key->type, destination); + if (!k) { + free(destination); + return NULL; + } + + return k; + } } DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { @@ -142,10 +165,12 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) return 0; - if (rr->key->type != DNS_TYPE_CNAME) + if (rr->key->type == DNS_TYPE_CNAME) + return dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); + else if (rr->key->type == DNS_TYPE_DNAME) + return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); + else return 0; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); } static void dns_resource_key_hash_func(const void *i, struct siphash *state) { diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index c02f2ec00f..c1601fb694 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -186,6 +186,7 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key); +DnsResourceKey* dns_resource_key_new_dname(const DnsResourceKey *key); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index e08143c3be..e6aad39c74 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -547,6 +547,73 @@ int dns_name_endswith(const char *name, const char *suffix) { } } +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { + const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; + int r, q, k, w; + + assert(name); + assert(old_suffix); + assert(new_suffix); + assert(ret); + + n = name; + s = old_suffix; + + for (;;) { + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + + if (!saved_before) + saved_before = n; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); + if (k < 0) + return k; + if (k > 0) + r = k; + + if (!saved_after) + saved_after = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); + if (w < 0) + return w; + if (w > 0) + q = w; + + if (r == 0 && q == 0) + break; + if (r == 0 && saved_after == n) { + *ret = NULL; /* doesn't match */ + return 0; + } + + ln[r] = ls[q] = 0; + + if (r != q || strcasecmp(ln, ls)) { + + /* Not the same, let's jump back, and try with the next label again */ + s = old_suffix; + n = saved_after; + saved_after = saved_before = NULL; + } + } + + /* Found it! Now generate the new name */ + prefix = strndupa(name, saved_before - name); + + r = dns_name_concat(prefix, new_suffix, ret); + if (r < 0) + return r; + + return 1; +} + int dns_name_between(const char *a, const char *b, const char *c) { int n; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index c6d7623e09..a68df330e6 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -62,6 +62,8 @@ int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); + int dns_name_reverse(int family, const union in_addr_union *a, char **ret); int dns_name_address(const char *p, int *family, union in_addr_union *a); @@ -76,3 +78,5 @@ bool dns_service_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_replace_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index a664e1c79c..b76a31a549 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -408,6 +408,25 @@ static void test_dns_service_split(void) { test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0); } +static void test_dns_name_change_suffix_one(const char *name, const char *old_suffix, const char *new_suffix, int r, const char *result) { + _cleanup_free_ char *s = NULL; + + assert_se(dns_name_change_suffix(name, old_suffix, new_suffix, &s) == r); + assert_se(streq_ptr(s, result)); +} + +static void test_dns_name_change_suffix(void) { + test_dns_name_change_suffix_one("foo.bar", "bar", "waldo", 1, "foo.waldo"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "foo.bar.waldi.quux", "piff.paff", 1, "piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "bar.waldi.quux", "piff.paff", 1, "foo.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "waldi.quux", "piff.paff", 1, "foo.bar.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "quux", "piff.paff", 1, "foo.bar.waldi.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "", "piff.paff", 1, "foo.bar.waldi.quux.piff.paff"); + test_dns_name_change_suffix_one("", "", "piff.paff", 1, "piff.paff"); + test_dns_name_change_suffix_one("", "", "", 1, ""); + test_dns_name_change_suffix_one("a", "b", "c", 0, NULL); +} + int main(int argc, char *argv[]) { test_dns_label_unescape(); @@ -427,6 +446,7 @@ int main(int argc, char *argv[]) { test_dns_srv_type_verify(); test_dns_service_join(); test_dns_service_split(); + test_dns_name_change_suffix(); return 0; }