diff options
Diffstat (limited to 'src/resolve/resolved-dns-question.c')
-rw-r--r-- | src/resolve/resolved-dns-question.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c new file mode 100644 index 0000000..5754c85 --- /dev/null +++ b/src/resolve/resolved-dns-question.c @@ -0,0 +1,552 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "resolved-dns-question.h" +#include "socket-util.h" + +DnsQuestion *dns_question_new(size_t n) { + DnsQuestion *q; + + if (n > UINT16_MAX) /* We can only place 64K key in an question section at max */ + n = UINT16_MAX; + + q = malloc0(offsetof(DnsQuestion, items) + sizeof(DnsQuestionItem) * n); + if (!q) + return NULL; + + q->n_ref = 1; + q->n_allocated = n; + + return q; +} + +static DnsQuestion *dns_question_free(DnsQuestion *q) { + DnsResourceKey *key; + + assert(q); + + DNS_QUESTION_FOREACH(key, q) + dns_resource_key_unref(key); + + return mfree(q); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsQuestion, dns_question, dns_question_free); + +int dns_question_add_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags) { + /* Insert without checking for duplicates. */ + + assert(key); + assert(q); + + if (q->n_keys >= q->n_allocated) + return -ENOSPC; + + q->items[q->n_keys++] = (DnsQuestionItem) { + .key = dns_resource_key_ref(key), + .flags = flags, + }; + return 0; +} + +static int dns_question_add_raw_all(DnsQuestion *a, DnsQuestion *b) { + DnsQuestionItem *item; + int r; + + DNS_QUESTION_FOREACH_ITEM(item, b) { + r = dns_question_add_raw(a, item->key, item->flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags) { + DnsQuestionItem *item; + int r; + + assert(key); + + if (!q) + return -ENOSPC; + + + DNS_QUESTION_FOREACH_ITEM(item, q) { + r = dns_resource_key_equal(item->key, key); + if (r < 0) + return r; + if (r > 0 && item->flags == flags) + return 0; + } + + return dns_question_add_raw(q, key, flags); +} + +static int dns_question_add_all(DnsQuestion *a, DnsQuestion *b) { + DnsQuestionItem *item; + int r; + + DNS_QUESTION_FOREACH_ITEM(item, b) { + r = dns_question_add(a, item->key, item->flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + DnsResourceKey *key; + int r; + + assert(rr); + + if (!q) + return 0; + + DNS_QUESTION_FOREACH(key, q) { + r = dns_resource_key_match_rr(key, rr, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + DnsResourceKey *key; + int r; + + assert(rr); + + if (!q) + return 0; + + if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) + return 0; + + DNS_QUESTION_FOREACH(key, q) { + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(key->type)) + return 0; + + r = dns_resource_key_match_cname_or_dname(key, rr->key, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_valid_for_query(DnsQuestion *q) { + const char *name; + size_t i; + int r; + + if (!q) + return 0; + + if (q->n_keys <= 0) + return 0; + + if (q->n_keys > 65535) + return 0; + + name = dns_resource_key_name(q->items[0].key); + if (!name) + return 0; + + /* Check that all keys in this question bear the same name */ + for (i = 0; i < q->n_keys; i++) { + assert(q->items[i].key); + + if (i > 0) { + r = dns_name_equal(dns_resource_key_name(q->items[i].key), name); + if (r <= 0) + return r; + } + + if (!dns_type_is_valid_query(q->items[i].key->type)) + return 0; + } + + return 1; +} + +int dns_question_contains_key(DnsQuestion *q, const DnsResourceKey *k) { + size_t j; + int r; + + assert(k); + + if (!q) + return 0; + + + for (j = 0; j < q->n_keys; j++) { + r = dns_resource_key_equal(q->items[j].key, k); + if (r != 0) + return r; + } + + return 0; +} + +static int dns_question_contains_item(DnsQuestion *q, const DnsQuestionItem *i) { + DnsQuestionItem *item; + int r; + + assert(i); + + DNS_QUESTION_FOREACH_ITEM(item, q) { + if (item->flags != i->flags) + continue; + r = dns_resource_key_equal(item->key, i->key); + if (r != 0) + return r; + } + + return false; +} + +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { + DnsQuestionItem *item; + int r; + + if (a == b) + return 1; + + if (!a) + return !b || b->n_keys == 0; + if (!b) + return a->n_keys == 0; + + /* Checks if all items in a are also contained b, and vice versa */ + + DNS_QUESTION_FOREACH_ITEM(item, a) { + r = dns_question_contains_item(b, item); + if (r <= 0) + return r; + } + DNS_QUESTION_FOREACH_ITEM(item, b) { + r = dns_question_contains_item(a, item); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + DnsResourceKey *key; + bool same = true; + int r; + + assert(cname); + assert(ret); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (dns_question_size(q) <= 0) { + *ret = NULL; + return 0; + } + + DNS_QUESTION_FOREACH(key, q) { + _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(key), 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(key), d); + if (r < 0) + return r; + + if (r == 0) { + same = false; + break; + } + } + + /* Fully the same, indicate we didn't do a thing */ + if (same) { + *ret = NULL; + return 0; + } + + n = dns_question_new(q->n_keys); + if (!n) + return -ENOMEM; + + /* Create a new question, and patch in the new name */ + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + k = dns_resource_key_new_redirect(key, cname); + if (!k) + return -ENOMEM; + + r = dns_question_add(n, k, 0); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(n); + + return 1; +} + +const char *dns_question_first_name(DnsQuestion *q) { + + if (!q) + return NULL; + + if (q->n_keys < 1) + return NULL; + + return dns_resource_key_name(q->items[0].key); +} + +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL; + int r; + + assert(ret); + assert(name); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + /* If IPv6 is off and the request has an unspecified lookup family, restrict it automatically to + * IPv4. */ + if (family == AF_UNSPEC && !socket_ipv6_is_enabled()) + family = AF_INET; + + if (convert_idna) { + r = dns_name_apply_idna(name, &buf); + if (r < 0) + return r; + if (r > 0 && !streq(name, buf)) + name = buf; + else + /* We did not manage to create convert the idna name, or it's + * the same as the original name. We assume the caller already + * created an unconverted question, so let's not repeat work + * unnecessarily. */ + return -EALREADY; + } + + q = dns_question_new(family == AF_UNSPEC ? 2 : 1); + if (!q) + return -ENOMEM; + + if (family != AF_INET6) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + } + + if (family != AF_INET) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(q); + + return 0; +} + +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *reverse = NULL; + int r; + + assert(ret); + assert(a); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + r = dns_name_reverse(family, a, &reverse); + if (r < 0) + return r; + + q = dns_question_new(1); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); + if (!key) + return -ENOMEM; + + reverse = NULL; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + + *ret = TAKE_PTR(q); + + return 0; +} + +int dns_question_new_service( + DnsQuestion **ret, + const char *service, + const char *type, + const char *domain, + bool with_txt, + bool convert_idna) { + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL, *joined = NULL; + const char *name; + int r; + + assert(ret); + + /* We support three modes of invocation: + * + * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service + * type and possibly a service name. If specified in this way we assume it's already IDNA converted if + * that's necessary. + * + * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD + * style prefix. In this case we'll IDNA convert the domain, if that's requested. + * + * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put + * together. The service name is never IDNA converted, and the domain is if requested. + * + * It's not supported to specify a service name without a type, or no domain name. + */ + + if (!domain) + return -EINVAL; + + if (type) { + if (convert_idna) { + r = dns_name_apply_idna(domain, &buf); + if (r < 0) + return r; + if (r > 0) + domain = buf; + } + + r = dns_service_join(service, type, domain, &joined); + if (r < 0) + return r; + + name = joined; + } else { + if (service) + return -EINVAL; + + name = domain; + } + + q = dns_question_new(1 + with_txt); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + + if (with_txt) { + dns_resource_key_unref(key); + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key, 0); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(q); + + return 0; +} + +/* + * This function is not used in the code base, but is useful when debugging. Do not delete. + */ +void dns_question_dump(DnsQuestion *question, FILE *f) { + DnsResourceKey *k; + + if (!f) + f = stdout; + + DNS_QUESTION_FOREACH(k, question) { + char buf[DNS_RESOURCE_KEY_STRING_MAX]; + + fputc('\t', f); + fputs(dns_resource_key_to_string(k, buf, sizeof(buf)), f); + fputc('\n', f); + } +} + +int dns_question_merge(DnsQuestion *a, DnsQuestion *b, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *k = NULL; + int r; + + assert(ret); + + if (a == b || dns_question_size(b) <= 0) { + *ret = dns_question_ref(a); + return 0; + } + + if (dns_question_size(a) <= 0) { + *ret = dns_question_ref(b); + return 0; + } + + k = dns_question_new(dns_question_size(a) + dns_question_size(b)); + if (!k) + return -ENOMEM; + + r = dns_question_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_question_add_all(k, b); + if (r < 0) + return r; + + *ret = TAKE_PTR(k); + return 0; +} |