Systemd/src/resolve/resolved-dns-question.c
Zbigniew Jędrzejewski-Szmek 11a1589223 tree-wide: drop license boilerplate
Files which are installed as-is (any .service and other unit files, .conf
files, .policy files, etc), are left as is. My assumption is that SPDX
identifiers are not yet that well known, so it's better to retain the
extended header to avoid any doubt.

I also kept any copyright lines. We can probably remove them, but it'd nice to
obtain explicit acks from all involved authors before doing that.
2018-04-06 18:58:55 +02:00

459 lines
12 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
***/
#include "alloc-util.h"
#include "dns-domain.h"
#include "dns-type.h"
#include "resolved-dns-question.h"
DnsQuestion *dns_question_new(unsigned n) {
DnsQuestion *q;
assert(n > 0);
q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n);
if (!q)
return NULL;
q->n_ref = 1;
q->n_allocated = n;
return q;
}
DnsQuestion *dns_question_ref(DnsQuestion *q) {
if (!q)
return NULL;
assert(q->n_ref > 0);
q->n_ref++;
return q;
}
DnsQuestion *dns_question_unref(DnsQuestion *q) {
if (!q)
return NULL;
assert(q->n_ref > 0);
if (q->n_ref == 1) {
unsigned i;
for (i = 0; i < q->n_keys; i++)
dns_resource_key_unref(q->keys[i]);
free(q);
} else
q->n_ref--;
return NULL;
}
int dns_question_add(DnsQuestion *q, DnsResourceKey *key) {
unsigned i;
int r;
assert(key);
if (!q)
return -ENOSPC;
for (i = 0; i < q->n_keys; i++) {
r = dns_resource_key_equal(q->keys[i], key);
if (r < 0)
return r;
if (r > 0)
return 0;
}
if (q->n_keys >= q->n_allocated)
return -ENOSPC;
q->keys[q->n_keys++] = dns_resource_key_ref(key);
return 0;
}
int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
unsigned i;
int r;
assert(rr);
if (!q)
return 0;
for (i = 0; i < q->n_keys; i++) {
r = dns_resource_key_match_rr(q->keys[i], 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) {
unsigned i;
int r;
assert(rr);
if (!q)
return 0;
if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME))
return 0;
for (i = 0; i < q->n_keys; i++) {
/* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
if (!dns_type_may_redirect(q->keys[i]->type))
return 0;
r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
if (r != 0)
return r;
}
return 0;
}
int dns_question_is_valid_for_query(DnsQuestion *q) {
const char *name;
unsigned 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->keys[0]);
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->keys[i]);
if (i > 0) {
r = dns_name_equal(dns_resource_key_name(q->keys[i]), name);
if (r <= 0)
return r;
}
if (!dns_type_is_valid_query(q->keys[i]->type))
return 0;
}
return 1;
}
int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) {
unsigned j;
int r;
assert(k);
if (!a)
return 0;
for (j = 0; j < a->n_keys; j++) {
r = dns_resource_key_equal(a->keys[j], k);
if (r != 0)
return r;
}
return 0;
}
int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
unsigned j;
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 keys in a are also contained b, and vice versa */
for (j = 0; j < a->n_keys; j++) {
r = dns_question_contains(b, a->keys[j]);
if (r <= 0)
return r;
}
for (j = 0; j < b->n_keys; j++) {
r = dns_question_contains(a, b->keys[j]);
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);
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->keys[0]);
}
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 (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 uncoverted 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);
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);
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);
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);
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);
if (r < 0)
return r;
}
*ret = TAKE_PTR(q);
return 0;
}