Merge pull request #2358 from poettering/dnssec16

sixteenth DNSSEC patch set
This commit is contained in:
Tom Gundersen
2016-01-19 01:44:20 +01:00
13 changed files with 815 additions and 418 deletions

View File

@@ -27,18 +27,6 @@
#include "resolved-def.h"
static int reply_query_state(DnsQuery *q) {
_cleanup_free_ char *ip = NULL;
const char *name;
int r;
if (q->request_address_valid) {
r = in_addr_to_string(q->request_family, &q->request_address, &ip);
if (r < 0)
return r;
name = ip;
} else
name = dns_question_first_name(q->question);
switch (q->state) {
@@ -74,7 +62,7 @@ static int reply_query_state(DnsQuery *q) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (q->answer_rcode == DNS_RCODE_NXDOMAIN)
sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name);
sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q));
else {
const char *rc, *n;
char p[3]; /* the rcode is 4 bits long */
@@ -86,7 +74,7 @@ static int reply_query_state(DnsQuery *q) {
}
n = strjoina(_BUS_ERROR_DNS, rc);
sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc);
sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
}
return sd_bus_reply_method_error(q->request, &error);
@@ -156,7 +144,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -177,7 +165,11 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
int ifindex;
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
DnsQuestion *question;
question = dns_query_question_for_protocol(q, q->answer_protocol);
r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
goto finish;
if (r == 0)
@@ -195,7 +187,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
}
if (added <= 0) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -239,7 +231,7 @@ static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus
}
static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
Manager *m = userdata;
const char *hostname;
int family, ifindex;
@@ -269,11 +261,15 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
if (r < 0)
return r;
r = dns_question_new_address(&question, family, hostname);
r = dns_question_new_address(&question_utf8, family, hostname, false);
if (r < 0)
return r;
r = dns_query_new(m, &q, question, ifindex, flags);
r = dns_question_new_address(&question_idna, family, hostname, true);
if (r < 0)
return r;
r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags);
if (r < 0)
return r;
@@ -298,6 +294,7 @@ fail:
static void bus_method_resolve_address_complete(DnsQuery *q) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
DnsQuestion *question;
DnsResourceRecord *rr;
unsigned added = 0;
int ifindex, r;
@@ -311,7 +308,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -327,20 +324,20 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
if (r < 0)
goto finish;
if (q->answer) {
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(q->question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
question = dns_query_question_for_protocol(q, q->answer_protocol);
r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
if (r < 0)
goto finish;
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
added ++;
}
r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
if (r < 0)
goto finish;
added ++;
}
if (added <= 0) {
@@ -411,7 +408,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -465,7 +462,10 @@ static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int i
static void bus_method_resolve_record_complete(DnsQuery *q) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
DnsResourceRecord *rr;
DnsQuestion *question;
unsigned added = 0;
int ifindex;
int r;
assert(q);
@@ -477,7 +477,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -493,27 +493,24 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
if (r < 0)
goto finish;
if (q->answer) {
DnsResourceRecord *rr;
int ifindex;
question = dns_query_question_for_protocol(q, q->answer_protocol);
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(q->question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
r = bus_message_append_rr(reply, rr, ifindex);
if (r < 0)
goto finish;
r = bus_message_append_rr(reply, rr, ifindex);
if (r < 0)
goto finish;
added ++;
}
added ++;
}
if (added <= 0) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -582,7 +579,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r < 0)
return r;
r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -622,13 +619,16 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
* record for the SRV record */
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
DnsResourceRecord *zz;
DnsQuestion *question;
if (aux->state != DNS_TRANSACTION_SUCCESS)
continue;
if (aux->auxiliary_result != 0)
continue;
r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
question = dns_query_question_for_protocol(aux, aux->answer_protocol);
r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
if (r < 0)
return r;
if (r == 0)
@@ -636,7 +636,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
DNS_ANSWER_FOREACH(zz, aux->answer) {
r = dns_question_matches_rr(aux->question, zz, NULL);
r = dns_question_matches_rr(question, zz, NULL);
if (r < 0)
return r;
if (r == 0)
@@ -673,6 +673,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
DnsResourceRecord *zz;
DnsQuestion *question;
int ifindex;
if (aux->state != DNS_TRANSACTION_SUCCESS)
@@ -680,7 +681,9 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if (aux->auxiliary_result != 0)
continue;
r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
question = dns_query_question_for_protocol(aux, aux->answer_protocol);
r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
if (r < 0)
return r;
if (r == 0)
@@ -688,7 +691,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) {
r = dns_question_matches_rr(aux->question, zz, NULL);
r = dns_question_matches_rr(question, zz, NULL);
if (r < 0)
return r;
if (r == 0)
@@ -746,8 +749,10 @@ static void resolve_service_all_complete(DnsQuery *q) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
DnsQuestion *question;
DnsResourceRecord *rr;
unsigned added = 0;
DnsQuery *aux;
unsigned added = false;
int r;
assert(q);
@@ -789,7 +794,7 @@ static void resolve_service_all_complete(DnsQuery *q) {
assert(bad->auxiliary_result != 0);
if (bad->auxiliary_result == -ELOOP) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(bad->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad));
goto finish;
}
@@ -810,31 +815,28 @@ static void resolve_service_all_complete(DnsQuery *q) {
if (r < 0)
goto finish;
if (q->answer) {
DnsResourceRecord *rr;
question = dns_query_question_for_protocol(q, q->answer_protocol);
DNS_ANSWER_FOREACH(rr, q->answer) {
r = dns_question_matches_rr(question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
DNS_ANSWER_FOREACH(rr, q->answer) {
r = dns_question_matches_rr(q->question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
r = append_srv(q, reply, rr);
if (r < 0)
goto finish;
if (r == 0) /* not an SRV record */
continue;
r = append_srv(q, reply, rr);
if (r < 0)
goto finish;
if (r == 0) /* not an SRV record */
continue;
if (!canonical)
canonical = dns_resource_record_ref(rr);
if (!canonical)
canonical = dns_resource_record_ref(rr);
added++;
}
added++;
}
if (added <= 0) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -846,20 +848,16 @@ static void resolve_service_all_complete(DnsQuery *q) {
if (r < 0)
goto finish;
if (q->answer) {
DnsResourceRecord *rr;
DNS_ANSWER_FOREACH(rr, q->answer) {
r = dns_question_matches_rr(question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
DNS_ANSWER_FOREACH(rr, q->answer) {
r = dns_question_matches_rr(q->question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
r = append_txt(reply, rr);
if (r < 0)
goto finish;
}
r = append_txt(reply, rr);
if (r < 0)
goto finish;
}
r = sd_bus_message_close_container(reply);
@@ -923,11 +921,11 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin
/* OK, we found an SRV record for the service. Let's resolve
* the hostname included in it */
r = dns_question_new_address(&question, q->request_family, rr->srv.name);
r = dns_question_new_address(&question, q->request_family, rr->srv.name, false);
if (r < 0)
return r;
r = dns_query_new(q->manager, &aux, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -961,8 +959,11 @@ fail:
}
static void bus_method_resolve_service_complete(DnsQuery *q) {
bool has_root_domain = false;
DnsResourceRecord *rr;
DnsQuestion *question;
unsigned found = 0;
int r;
int ifindex, r;
assert(q);
@@ -973,7 +974,7 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -981,53 +982,48 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
if (q->answer) {
bool has_root_domain = false;
DnsResourceRecord *rr;
int ifindex;
question = dns_query_question_for_protocol(q, q->answer_protocol);
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
if (rr->key->type != DNS_TYPE_SRV)
continue;
if (dns_name_is_root(rr->srv.name)) {
has_root_domain = true;
continue;
}
if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
q->block_all_complete ++;
r = resolve_service_hostname(q, rr, ifindex);
q->block_all_complete --;
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
r = dns_question_matches_rr(q->question, rr, NULL);
if (r < 0)
goto finish;
if (r == 0)
continue;
if (rr->key->type != DNS_TYPE_SRV)
continue;
if (dns_name_is_root(rr->srv.name)) {
has_root_domain = true;
continue;
}
if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
q->block_all_complete ++;
r = resolve_service_hostname(q, rr, ifindex);
q->block_all_complete --;
if (r < 0)
goto finish;
}
found++;
}
if (has_root_domain && found == 0) {
/* If there's exactly one SRV RR and it uses
* the root domain as host name, then the
* service is explicitly not offered on the
* domain. Report this as a recognizable
* error. See RFC 2782, Section "Usage
* Rules". */
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_question_first_name(q->question));
goto finish;
}
found++;
}
if (has_root_domain && found <= 0) {
/* If there's exactly one SRV RR and it uses
* the root domain as host name, then the
* service is explicitly not offered on the
* domain. Report this as a recognizable
* error. See RFC 2782, Section "Usage
* Rules". */
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q));
goto finish;
}
if (found <= 0) {
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -1045,8 +1041,8 @@ finish:
}
static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
const char *name, *type, *domain, *joined;
_cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
const char *name, *type, *domain;
_cleanup_free_ char *n = NULL;
Manager *m = userdata;
int family, ifindex;
@@ -1068,10 +1064,8 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (isempty(name))
name = NULL;
else {
if (!dns_service_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
}
else if (!dns_service_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
if (isempty(type))
type = NULL;
@@ -1091,23 +1085,15 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
if (type) {
/* If the type is specified, we generate the full domain name to look up ourselves */
r = dns_service_join(name, type, domain, &n);
if (r < 0)
return r;
joined = n;
} else
/* If no type is specified, we assume the domain
* contains the full domain name to lookup already */
joined = domain;
r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT));
r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false);
if (r < 0)
return r;
r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true);
if (r < 0)
return r;
r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;

View File

@@ -41,9 +41,6 @@
* - enable by default
* - Allow clients to request DNSSEC even if DNSSEC is off
* - make sure when getting an NXDOMAIN response through CNAME, we still process the first CNAMEs in the packet
* - update test-complex to also do ResolveAddress lookups
* - extend complex test to check "xn--kprw13d." DNAME domain
* - rework IDNA stuff: only to IDNA for ResolveAddress and ResolveService, and prepare a pair of lookup keys then, never do IDNA comparisons after that
* */
#define VERIFY_RRS_MAX 256
@@ -1005,16 +1002,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return r;
if (r == 0)
break;
if (r > 0) {
int k;
/* DNSSEC validation is always done on the ASCII version of the label */
k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (buffer_max < (size_t) r + 2)
return -ENOBUFS;

View File

@@ -499,7 +499,6 @@ int dns_packet_append_name(
const char *z = name;
char label[DNS_LABEL_MAX];
size_t n = 0;
int k;
if (allow_compression)
n = PTR_TO_SIZE(hashmap_get(p->names, name));
@@ -519,17 +518,6 @@ int dns_packet_append_name(
if (r < 0)
goto fail;
if (p->protocol == DNS_PROTOCOL_DNS)
k = dns_label_apply_idna(label, r, label, sizeof(label));
else
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0) {
r = k;
goto fail;
}
if (k > 0)
r = k;
r = dns_packet_append_label(p, label, r, canonical_candidate, &n);
if (r < 0)
goto fail;

View File

@@ -24,6 +24,7 @@
#include "hostname-util.h"
#include "local-addresses.h"
#include "resolved-dns-query.h"
#include "string-util.h"
/* How long to wait for the query in total */
#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
@@ -217,6 +218,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
}
static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
DnsQuestion *question;
DnsResourceKey *key;
int n = 0, r;
@@ -224,8 +226,10 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
dns_query_candidate_stop(c);
question = dns_query_question_for_protocol(c->query, c->scope->protocol);
/* Create one transaction per question key */
DNS_QUESTION_FOREACH(key, c->query->question) {
DNS_QUESTION_FOREACH(key, question) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL;
if (c->search_domain) {
@@ -305,6 +309,25 @@ static void dns_query_stop(DnsQuery *q) {
dns_query_candidate_stop(c);
}
static void dns_query_free_candidates(DnsQuery *q) {
assert(q);
while (q->candidates)
dns_query_candidate_free(q->candidates);
}
static void dns_query_reset_answer(DnsQuery *q) {
assert(q);
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = 0;
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_authenticated = false;
q->answer_protocol = _DNS_PROTOCOL_INVALID;
q->answer_family = AF_UNSPEC;
q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain);
}
DnsQuery *dns_query_free(DnsQuery *q) {
if (!q)
return NULL;
@@ -318,16 +341,18 @@ DnsQuery *dns_query_free(DnsQuery *q) {
LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q);
}
while (q->candidates)
dns_query_candidate_free(q->candidates);
dns_query_free_candidates(q);
dns_question_unref(q->question);
dns_answer_unref(q->answer);
dns_search_domain_unref(q->answer_search_domain);
dns_question_unref(q->question_idna);
dns_question_unref(q->question_utf8);
dns_query_reset_answer(q);
sd_bus_message_unref(q->request);
sd_bus_track_unref(q->bus_track);
free(q->request_address_string);
if (q->manager) {
LIST_REMOVE(queries, q->manager->dns_queries, q);
q->manager->n_dns_queries--;
@@ -338,17 +363,50 @@ DnsQuery *dns_query_free(DnsQuery *q) {
return NULL;
}
int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) {
int dns_query_new(
Manager *m,
DnsQuery **ret,
DnsQuestion *question_utf8,
DnsQuestion *question_idna,
int ifindex, uint64_t flags) {
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
unsigned i;
DnsResourceKey *key;
bool good = false;
int r;
assert(m);
assert(question);
r = dns_question_is_valid_for_query(question);
if (dns_question_size(question_utf8) > 0) {
r = dns_question_is_valid_for_query(question_utf8);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
good = true;
}
/* If the IDNA and UTF8 questions are the same, merge their references */
r = dns_question_is_equal(question_idna, question_utf8);
if (r < 0)
return r;
if (r > 0)
question_idna = question_utf8;
else {
if (dns_question_size(question_idna) > 0) {
r = dns_question_is_valid_for_query(question_idna);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
good = true;
}
}
if (!good) /* don't allow empty queries */
return -EINVAL;
if (m->n_dns_queries >= QUERIES_MAX)
return -EBUSY;
@@ -357,20 +415,40 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex
if (!q)
return -ENOMEM;
q->question = dns_question_ref(question);
q->question_utf8 = dns_question_ref(question_utf8);
q->question_idna = dns_question_ref(question_idna);
q->ifindex = ifindex;
q->flags = flags;
q->answer_family = AF_UNSPEC;
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_protocol = _DNS_PROTOCOL_INVALID;
q->answer_family = AF_UNSPEC;
for (i = 0; i < question->n_keys; i++) {
_cleanup_free_ char *p;
/* First dump UTF8 question */
DNS_QUESTION_FOREACH(key, question_utf8) {
_cleanup_free_ char *p = NULL;
r = dns_resource_key_to_string(question->keys[i], &p);
r = dns_resource_key_to_string(key, &p);
if (r < 0)
return r;
log_debug("Looking up RR for %s", p);
log_debug("Looking up RR for %s.", strstrip(p));
}
/* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */
DNS_QUESTION_FOREACH(key, question_idna) {
_cleanup_free_ char *p = NULL;
r = dns_question_contains(question_utf8, key);
if (r < 0)
return r;
if (r > 0)
continue;
r = dns_resource_key_to_string(key, &p);
if (r < 0)
return r;
log_debug("Looking up IDNA RR for %s.", strstrip(p));
}
LIST_PREPEND(queries, m->dns_queries, q);
@@ -446,7 +524,7 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
/* If this a single-label domain on DNS, we might append a suitable search domain first. */
if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) {
r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna));
if (r < 0)
goto fail;
if (r > 0) {
@@ -534,7 +612,7 @@ static int dns_type_to_af(uint16_t t) {
}
}
static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
int r;
assert(q);
@@ -590,7 +668,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to,
return dns_answer_add(*answer, rr, ifindex, flags);
}
static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
int r;
assert(q);
@@ -682,7 +760,7 @@ static int answer_add_addresses_ptr(
return 0;
}
static int synthesize_system_hostname_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
_cleanup_free_ struct local_address *addresses = NULL;
int n = 0, af;
@@ -766,7 +844,7 @@ static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_ad
return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address);
}
static int synthesize_gateway_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
_cleanup_free_ struct local_address *addresses = NULL;
int n = 0, af;
@@ -801,7 +879,7 @@ static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union
static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
unsigned i;
DnsResourceKey *key;
int r;
assert(q);
@@ -816,39 +894,39 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
DNS_TRANSACTION_ATTEMPTS_MAX_REACHED))
return 0;
for (i = 0; i < q->question->n_keys; i++) {
DNS_QUESTION_FOREACH(key, q->question_utf8) {
union in_addr_union address;
const char *name;
int af;
if (q->question->keys[i]->class != DNS_CLASS_IN &&
q->question->keys[i]->class != DNS_CLASS_ANY)
if (key->class != DNS_CLASS_IN &&
key->class != DNS_CLASS_ANY)
continue;
name = DNS_RESOURCE_KEY_NAME(q->question->keys[i]);
name = DNS_RESOURCE_KEY_NAME(key);
if (is_localhost(name)) {
r = synthesize_localhost_rr(q, q->question->keys[i], &answer);
r = synthesize_localhost_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost RRs: %m");
} else if (manager_is_own_hostname(q->manager, name)) {
r = synthesize_system_hostname_rr(q, q->question->keys[i], &answer);
r = synthesize_system_hostname_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize system hostname RRs: %m");
} else if (is_gateway_hostname(name)) {
r = synthesize_gateway_rr(q, q->question->keys[i], &answer);
r = synthesize_gateway_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize gateway RRs: %m");
} else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) ||
dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) {
r = synthesize_localhost_ptr(q, q->question->keys[i], &answer);
r = synthesize_localhost_ptr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m");
@@ -884,7 +962,6 @@ int dns_query_go(DnsQuery *q) {
DnsScopeMatch found = DNS_SCOPE_NO;
DnsScope *s, *first = NULL;
DnsQueryCandidate *c;
const char *name;
int r;
assert(q);
@@ -892,13 +969,13 @@ int dns_query_go(DnsQuery *q) {
if (q->state != DNS_TRANSACTION_NULL)
return 0;
assert(q->question);
assert(q->question->n_keys > 0);
name = dns_question_first_name(q->question);
LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
DnsScopeMatch match;
const char *name;
name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol));
if (!name)
continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
if (match < 0)
@@ -934,6 +1011,11 @@ int dns_query_go(DnsQuery *q) {
LIST_FOREACH(scopes, s, first->scopes_next) {
DnsScopeMatch match;
const char *name;
name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol));
if (!name)
continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
if (match < 0)
@@ -1115,8 +1197,8 @@ void dns_query_ready(DnsQuery *q) {
}
static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) {
_cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
int r;
_cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL;
int r, k;
assert(q);
@@ -1124,24 +1206,51 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
r = dns_question_cname_redirect(q->question, cname, &nq);
r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna);
if (r < 0)
return r;
else if (r > 0)
log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna));
log_debug("Following CNAME/DNAME %s → %s", dns_question_first_name(q->question), dns_question_first_name(nq));
k = dns_question_is_equal(q->question_idna, q->question_utf8);
if (k < 0)
return r;
if (k > 0) {
/* Same question? Shortcut new question generation */
nq_utf8 = dns_question_ref(nq_idna);
k = r;
} else {
k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8);
if (k < 0)
return k;
else if (k > 0)
log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8));
}
dns_question_unref(q->question);
q->question = nq;
nq = NULL;
if (r == 0 && k == 0) /* No actual cname happened? */
return -ELOOP;
dns_query_stop(q);
dns_question_unref(q->question_idna);
q->question_idna = nq_idna;
nq_idna = NULL;
dns_question_unref(q->question_utf8);
q->question_utf8 = nq_utf8;
nq_utf8 = NULL;
dns_query_free_candidates(q);
dns_query_reset_answer(q);
q->state = DNS_TRANSACTION_NULL;
/* Turn off searching for the new name */
q->flags |= SD_RESOLVED_NO_SEARCH;
return 0;
}
int dns_query_process_cname(DnsQuery *q) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL;
DnsQuestion *question;
DnsResourceRecord *rr;
int r;
@@ -1150,15 +1259,16 @@ int dns_query_process_cname(DnsQuery *q) {
if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL))
return DNS_QUERY_NOMATCH;
DNS_ANSWER_FOREACH(rr, q->answer) {
question = dns_query_question_for_protocol(q, q->answer_protocol);
r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
DNS_ANSWER_FOREACH(rr, q->answer) {
r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
return r;
if (r > 0)
return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */
r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
return r;
if (r > 0 && !cname)
@@ -1219,3 +1329,42 @@ int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) {
return 0;
}
DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) {
assert(q);
switch (protocol) {
case DNS_PROTOCOL_DNS:
return q->question_idna;
case DNS_PROTOCOL_MDNS:
case DNS_PROTOCOL_LLMNR:
return q->question_utf8;
default:
return NULL;
}
}
const char *dns_query_string(DnsQuery *q) {
const char *name;
int r;
/* Returns a somewhat useful human-readable lookup key string for this query */
if (q->request_address_string)
return q->request_address_string;
if (q->request_address_valid) {
r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string);
if (r >= 0)
return q->request_address_string;
}
name = dns_question_first_name(q->question_utf8);
if (name)
return name;
return dns_question_first_name(q->question_idna);
}

View File

@@ -59,7 +59,13 @@ struct DnsQuery {
unsigned n_auxiliary_queries;
int auxiliary_result;
DnsQuestion *question;
/* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even
* on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their
* domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly
* relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */
DnsQuestion *question_idna;
DnsQuestion *question_utf8;
uint64_t flags;
int ifindex;
@@ -84,6 +90,7 @@ struct DnsQuery {
bool request_address_valid;
union in_addr_union request_address;
unsigned block_all_complete;
char *request_address_string;
/* Completion callback */
void (*complete)(DnsQuery* q);
@@ -104,7 +111,7 @@ enum {
DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c);
void dns_query_candidate_notify(DnsQueryCandidate *c);
int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags);
int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags);
DnsQuery *dns_query_free(DnsQuery *q);
int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for);
@@ -116,4 +123,8 @@ int dns_query_process_cname(DnsQuery *q);
int dns_query_bus_track(DnsQuery *q, sd_bus_message *m);
DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol);
const char *dns_query_string(DnsQuery *q);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free);

View File

@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
#include "dns-type.h"
#include "resolved-dns-question.h"
DnsQuestion *dns_question_new(unsigned n) {
@@ -107,7 +108,7 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *s
return 0;
}
int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
unsigned i;
int r;
@@ -116,7 +117,14 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char
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;
@@ -144,18 +152,23 @@ int dns_question_is_valid_for_query(DnsQuestion *q) {
return 0;
/* Check that all keys in this question bear the same name */
for (i = 1; i < q->n_keys; i++) {
for (i = 0; i < q->n_keys; i++) {
assert(q->keys[i]);
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name);
if (r <= 0)
return r;
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, DnsResourceKey *k) {
int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) {
unsigned j;
int r;
@@ -177,6 +190,9 @@ 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)
@@ -201,32 +217,27 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) {
_cleanup_(dns_question_unrefp) DnsQuestion *n = NULL;
DnsResourceKey *key;
bool same = true;
unsigned i;
int r;
assert(cname);
assert(ret);
assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
if (!q) {
n = dns_question_new(0);
if (!n)
return -ENOMEM;
*ret = n;
n = 0;
if (dns_question_size(q) <= 0) {
*ret = NULL;
return 0;
}
for (i = 0; i < q->n_keys; i++) {
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(q->keys[i]), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination);
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)
@@ -235,7 +246,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
d = destination;
}
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), d);
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d);
if (r < 0)
return r;
@@ -245,9 +256,9 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
}
}
/* Fully the same, indicate we didn't do a thing */
if (same) {
/* Shortcut, the names are already right */
*ret = dns_question_ref(q);
*ret = NULL;
return 0;
}
@@ -256,10 +267,10 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
return -ENOMEM;
/* Create a new question, and patch in the new name */
for (i = 0; i < q->n_keys; i++) {
DNS_QUESTION_FOREACH(key, q) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
k = dns_resource_key_new_redirect(q->keys[i], cname);
k = dns_resource_key_new_redirect(key, cname);
if (!k)
return -ENOMEM;
@@ -285,8 +296,9 @@ const char *dns_question_first_name(DnsQuestion *q) {
return DNS_RESOURCE_KEY_NAME(q->keys[0]);
}
int dns_question_new_address(DnsQuestion **ret, int family, const char *name) {
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);
@@ -295,6 +307,14 @@ int dns_question_new_address(DnsQuestion **ret, int family, const char *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;
name = buf;
}
q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
if (!q)
return -ENOMEM;
@@ -365,13 +385,60 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_
return 0;
}
int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt) {
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);
assert(name);
/* 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;
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)

View File

@@ -38,22 +38,26 @@ DnsQuestion *dns_question_new(unsigned n);
DnsQuestion *dns_question_ref(DnsQuestion *q);
DnsQuestion *dns_question_unref(DnsQuestion *q);
int dns_question_new_address(DnsQuestion **ret, int family, const char *name);
int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna);
int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a);
int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt);
int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna);
int dns_question_add(DnsQuestion *q, DnsResourceKey *key);
int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain);
int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain);
int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain);
int dns_question_is_valid_for_query(DnsQuestion *q);
int dns_question_contains(DnsQuestion *a, DnsResourceKey *k);
int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k);
int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b);
int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret);
const char *dns_question_first_name(DnsQuestion *q);
static inline unsigned dns_question_size(DnsQuestion *q) {
return q ? q->n_keys : 0;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
#define _DNS_QUESTION_FOREACH(u, key, q) \

View File

@@ -375,9 +375,18 @@ static void dns_server_reset_counters(DnsServer *s) {
s->n_failed_tcp = 0;
s->packet_failed = false;
s->packet_truncated = false;
s->packet_bad_opt = false;
s->packet_rrsig_missing = false;
s->verified_usec = 0;
/* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the
* grace period ends, but not when lowering the possible feature level, as a lower level feature level should
* not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's
* indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that
* either.
*
* This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A),
* but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not
* reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be
* incomplete. */
}
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
@@ -387,8 +396,12 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
dns_server_grace_period_expired(s)) {
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
dns_server_reset_counters(s);
s->packet_bad_opt = false;
s->packet_rrsig_missing = false;
log_info("Grace period over, resuming full feature set (%s) for DNS server %s.",
dns_server_feature_level_to_string(s->possible_feature_level),
dns_server_string(s));

View File

@@ -31,6 +31,8 @@
#include "resolved-llmnr.h"
#include "string-table.h"
#define TRANSACTIONS_MAX 4096
static void dns_transaction_reset_answer(DnsTransaction *t) {
assert(t);
@@ -135,6 +137,22 @@ bool dns_transaction_gc(DnsTransaction *t) {
return true;
}
static uint16_t pick_new_id(Manager *m) {
uint16_t new_id;
/* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of
* transactions, and it's much lower than the space of IDs. */
assert_cc(TRANSACTIONS_MAX < 0xFFFF);
do
random_bytes(&new_id, sizeof(new_id));
while (new_id == 0 ||
hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id)));
return new_id;
}
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
_cleanup_(dns_transaction_freep) DnsTransaction *t = NULL;
int r;
@@ -153,6 +171,9 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY)
return -EOPNOTSUPP;
if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX)
return -EBUSY;
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@@ -172,11 +193,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->key = dns_resource_key_ref(key);
t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
/* Find a fresh, unused transaction id */
do
random_bytes(&t->id, sizeof(t->id));
while (t->id == 0 ||
hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id)));
t->id = pick_new_id(s->manager);
r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t);
if (r < 0) {
@@ -203,6 +220,22 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
return 0;
}
static void dns_transaction_shuffle_id(DnsTransaction *t) {
uint16_t new_id;
assert(t);
/* Pick a new ID for this transaction. */
new_id = pick_new_id(t->scope->manager);
assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0);
log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id);
t->id = new_id;
/* Make sure we generate a new packet with the new ID */
t->sent = dns_packet_unref(t->sent);
}
static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
_cleanup_free_ char *pretty = NULL;
DnsZoneItem *z;
@@ -377,7 +410,8 @@ static int dns_transaction_maybe_restart(DnsTransaction *t) {
OPT RR or DO bit. One of these cases is documented here, for example:
https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */
log_debug("Server feature level is now lower than when we began our transaction. Restarting.");
log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID.");
dns_transaction_shuffle_id(t);
return dns_transaction_go(t);
}
@@ -556,13 +590,76 @@ static bool dns_transaction_dnssec_is_live(DnsTransaction *t) {
return false;
}
static int dns_transaction_dnssec_ready(DnsTransaction *t) {
DnsTransaction *dt;
Iterator i;
assert(t);
/* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still
* ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */
SET_FOREACH(dt, t->dnssec_transactions, i) {
switch (dt->state) {
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_VALIDATING:
/* Still ongoing */
return 0;
case DNS_TRANSACTION_RCODE_FAILURE:
if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) {
log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode));
goto fail;
}
/* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously
* return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when
* asking for parent SOA or similar RRs to make unsigned proofs. */
case DNS_TRANSACTION_SUCCESS:
/* All good. */
break;
case DNS_TRANSACTION_DNSSEC_FAILED:
/* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
* validationr result */
log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
default:
log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state));
goto fail;
}
}
/* All is ready, we can go and validate */
return 1;
fail:
t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY;
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return 0;
}
static void dns_transaction_process_dnssec(DnsTransaction *t) {
int r;
assert(t);
/* Are there ongoing DNSSEC transactions? If so, let's wait for them. */
if (dns_transaction_dnssec_is_live(t))
r = dns_transaction_dnssec_ready(t);
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return;
}
if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */
return;
/* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better
@@ -870,12 +967,12 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
return 0;
}
if (r == 0) {
log_debug("Received inappropriate DNS packet as response, ignoring: %m");
log_debug("Received inappropriate DNS packet as response, ignoring.");
return 0;
}
if (DNS_PACKET_ID(p) != t->id) {
log_debug("Received packet with incorrect transaction ID, ignoring: %m");
log_debug("Received packet with incorrect transaction ID, ignoring.");
return 0;
}
@@ -1932,69 +2029,15 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
}
void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
int r;
assert(t);
assert(source);
if (!IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
return;
/* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING,
we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If
the state is VALIDATING however, we should check if we are complete now. */
/* Invoked whenever any of our auxiliary DNSSEC transactions
completed its work. We copy any RRs from that transaction
over into our list of validated keys -- but only if the
answer is authenticated.
Note that we fail our transaction if the auxiliary
transaction failed, except on NXDOMAIN. This is because
some broken DNS servers (Akamai...) will return NXDOMAIN
for empty non-terminals. */
switch (source->state) {
case DNS_TRANSACTION_DNSSEC_FAILED:
log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(source->answer_dnssec_result));
t->answer_dnssec_result = source->answer_dnssec_result; /* Copy error code over */
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
break;
case DNS_TRANSACTION_RCODE_FAILURE:
if (source->answer_rcode != DNS_RCODE_NXDOMAIN) {
log_debug("Auxiliary DNSSEC RR query failed with rcode=%i.", source->answer_rcode);
goto fail;
}
/* fall-through: NXDOMAIN is good enough for us */
case DNS_TRANSACTION_SUCCESS:
if (source->answer_authenticated) {
r = dns_answer_extend(&t->validated_keys, source->answer);
if (r < 0) {
log_error_errno(r, "Failed to merge validated DNSSEC key data: %m");
goto fail;
}
}
/* If the state is still PENDING, we are still in the loop
* that adds further DNSSEC transactions, hence don't check if
* we are ready yet. If the state is VALIDATING however, we
* should check if we are complete now. */
if (t->state == DNS_TRANSACTION_VALIDATING)
dns_transaction_process_dnssec(t);
break;
default:
log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(source->state));
goto fail;
}
return;
fail:
t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY;
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
if (t->state == DNS_TRANSACTION_VALIDATING)
dns_transaction_process_dnssec(t);
}
static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
@@ -2437,6 +2480,31 @@ static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) {
return 0;
}
static int dns_transaction_copy_validated(DnsTransaction *t) {
DnsTransaction *dt;
Iterator i;
int r;
assert(t);
/* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */
SET_FOREACH(dt, t->dnssec_transactions, i) {
if (DNS_TRANSACTION_IS_LIVE(dt->state))
continue;
if (!dt->answer_authenticated)
continue;
r = dns_answer_extend(&t->validated_keys, dt->answer);
if (r < 0)
return r;
}
return 0;
}
int dns_transaction_validate_dnssec(DnsTransaction *t) {
_cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
enum {
@@ -2487,13 +2555,18 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
/* Third, copy all RRs we acquired successfully from auxiliary RRs over. */
r = dns_transaction_copy_validated(t);
if (r < 0)
return r;
/* Second, see if there are DNSKEYs we already know a
* validated DS for. */
r = dns_transaction_validate_dnskey_by_ds(t);
if (r < 0)
return r;
/* Third, remove all DNSKEY and DS RRs again that our trust
/* Fourth, remove all DNSKEY and DS RRs again that our trust
* anchor says are revoked. After all we might have marked
* some keys revoked above, but they might still be lingering
* in our validated_keys list. */

View File

@@ -19,8 +19,11 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <netinet/ip.h>
#include "sd-bus.h"
#include "af-list.h"
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "dns-type.h"
@@ -30,7 +33,28 @@
#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
static void test_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) {
static void prefix_random(const char *name, char **ret) {
uint64_t i, u;
char *m = NULL;
u = 1 + (random_u64() & 3);
for (i = 0; i < u; i++) {
_cleanup_free_ char *b = NULL;
char *x;
assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64()));
x = strjoin(b, ".", name, NULL);
assert_se(x);
free(m);
m = x;
}
*ret = m;
}
static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *m = NULL;
@@ -38,21 +62,8 @@ static void test_lookup(sd_bus *bus, const char *name, uint16_t type, const char
/* If the name starts with a dot, we prefix one to three random labels */
if (startswith(name, ".")) {
uint64_t i, u;
u = 1 + (random_u64() & 3);
name ++;
for (i = 0; i < u; i++) {
_cleanup_free_ char *b = NULL;
char *x;
assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64()));
x = strjoin(b, ".", name, NULL);
assert_se(x);
free(m);
name = m = x;
}
prefix_random(name + 1, &m);
name = m;
}
assert_se(sd_bus_message_new_method_call(
@@ -77,6 +88,44 @@ static void test_lookup(sd_bus *bus, const char *name, uint16_t type, const char
}
}
static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *m = NULL;
const char *af;
int r;
af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family);
/* If the name starts with a dot, we prefix one to three random labels */
if (startswith(name, ".")) {
prefix_random(name + 1, &m);
name = m;
}
assert_se(sd_bus_message_new_method_call(
bus,
&req,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"ResolveHostname") >= 0);
assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0);
r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
if (r < 0) {
assert_se(result);
assert_se(sd_bus_error_has_name(&error, result));
log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name);
} else {
assert_se(!result);
log_info("[OK] %s/%s succeeded.", name, af);
}
}
int main(int argc, char* argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -90,57 +139,100 @@ int main(int argc, char* argv[]) {
assert_se(sd_bus_open_system(&bus) >= 0);
/* Normally signed */
test_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL);
test_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL);
test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL);
test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL);
/* Normally signed, NODATA */
test_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
/* Invalid signature */
test_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED);
/* Invalid signature, RSA, wildcard */
test_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED);
/* Invalid signature, ECDSA, wildcard */
test_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED);
/* NXDOMAIN in NSEC domain */
test_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
/* wildcard, NSEC zone */
test_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL);
test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL);
/* wildcard, NSEC zone, NODATA */
test_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
/* wildcard, NSEC3 zone */
test_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL);
test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL);
/* wildcard, NSEC3 zone, NODATA */
test_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
/* wildcard, NSEC zone, CNAME */
test_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL);
test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL);
test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL);
/* wildcard, NSEC zone, NODATA, CNAME */
test_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
/* wildcard, NSEC3 zone, CNAME */
test_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL);
test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL);
test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL);
test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL);
/* wildcard, NSEC3 zone, NODATA, CNAME */
test_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
/* NODATA due to empty non-terminal in NSEC domain */
test_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR);
test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR);
test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR);
test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR);
test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR);
/* NXDOMAIN in NSEC root zone: */
test_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
/* NXDOMAIN in NSEC3 .com zone: */
test_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
/* Unsigned A */
test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL);
test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL);
test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL);
test_hostname_lookup(bus, "poettering.de", AF_INET, NULL);
test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL);
#if HAVE_LIBIDN
/* Unsigned A with IDNA conversion necessary */
test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL);
test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL);
test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL);
#endif
/* DNAME, pointing to NXDOMAIN */
test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
return 0;
}

View File

@@ -413,7 +413,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
for (;;) {
char label[DNS_LABEL_MAX];
int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
@@ -432,12 +431,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
break;
}
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0)
return k;
if (k > 0)
r = k;
if (_ret) {
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
@@ -487,7 +480,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
for (;;) {
char label[DNS_LABEL_MAX+1];
int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
@@ -495,12 +487,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
if (r == 0)
break;
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0)
break;
if (k > 0)
r = k;
ascii_strlower_n(label, r);
siphash24_compress(label, r, state);
siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
@@ -512,7 +498,7 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
int dns_name_compare_func(const void *a, const void *b) {
const char *x, *y;
int r, q, k, w;
int r, q;
assert(a);
assert(b);
@@ -531,22 +517,6 @@ int dns_name_compare_func(const void *a, const void *b) {
if (r < 0 || q < 0)
return r - q;
if (r > 0)
k = dns_label_undo_idna(la, r, la, sizeof(la));
else
k = 0;
if (q > 0)
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
else
w = 0;
if (k < 0 || w < 0)
return k - w;
if (k > 0)
r = k;
if (w > 0)
q = w;
r = ascii_strcasecmp_nn(la, r, lb, q);
if (r != 0)
return r;
@@ -558,24 +528,6 @@ const struct hash_ops dns_name_hash_ops = {
.compare = dns_name_compare_func
};
static int dns_label_unescape_undo_idna(const char **name, char *dest, size_t sz) {
int r, k;
/* Clobbers all arguments on failure... */
r = dns_label_unescape(name, dest, sz);
if (r <= 0)
return r;
k = dns_label_undo_idna(dest, r, dest, sz);
if (k < 0)
return k;
if (k == 0) /* not an IDNA name */
return r;
return k;
}
int dns_name_equal(const char *x, const char *y) {
int r, q;
@@ -585,11 +537,11 @@ int dns_name_equal(const char *x, const char *y) {
for (;;) {
char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
r = dns_label_unescape_undo_idna(&x, la, sizeof(la));
r = dns_label_unescape(&x, la, sizeof(la));
if (r < 0)
return r;
q = dns_label_unescape_undo_idna(&y, lb, sizeof(lb));
q = dns_label_unescape(&y, lb, sizeof(lb));
if (q < 0)
return q;
@@ -616,14 +568,14 @@ int dns_name_endswith(const char *name, const char *suffix) {
for (;;) {
char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
r = dns_label_unescape_undo_idna(&n, ln, sizeof(ln));
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
if (!saved_n)
saved_n = n;
q = dns_label_unescape_undo_idna(&s, ls, sizeof(ls));
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
@@ -655,13 +607,13 @@ int dns_name_startswith(const char *name, const char *prefix) {
for (;;) {
char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
r = dns_label_unescape_undo_idna(&p, lp, sizeof(lp));
r = dns_label_unescape(&p, lp, sizeof(lp));
if (r < 0)
return r;
if (r == 0)
return true;
q = dns_label_unescape_undo_idna(&n, ln, sizeof(ln));
q = dns_label_unescape(&n, ln, sizeof(ln));
if (q < 0)
return q;
@@ -690,14 +642,14 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
if (!saved_before)
saved_before = n;
r = dns_label_unescape_undo_idna(&n, ln, sizeof(ln));
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
if (!saved_after)
saved_after = n;
q = dns_label_unescape_undo_idna(&s, ls, sizeof(ls));
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
@@ -1286,12 +1238,12 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
}
x = a_labels[n - 1 - k];
r = dns_label_unescape_undo_idna(&x, la, sizeof(la));
r = dns_label_unescape(&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));
q = dns_label_unescape(&y, lb, sizeof(lb));
if (q < 0)
return q;
@@ -1303,3 +1255,55 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
k++;
}
}
int dns_name_apply_idna(const char *name, char **ret) {
_cleanup_free_ char *buf = NULL;
size_t n = 0, allocated = 0;
bool first = true;
int r, q;
assert(name);
assert(ret);
for (;;) {
char label[DNS_LABEL_MAX];
r = dns_label_unescape(&name, label, sizeof(label));
if (r < 0)
return r;
if (r == 0)
break;
q = dns_label_apply_idna(label, r, label, sizeof(label));
if (q < 0)
return q;
if (q > 0)
r = q;
if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
if (first)
first = false;
else
buf[n++] = '.';
n +=r;
}
if (n > DNS_HOSTNAME_MAX)
return -EINVAL;
if (!GREEDY_REALLOC(buf, allocated, n + 1))
return -ENOMEM;
buf[n] = 0;
*ret = buf;
buf = NULL;
return (int) n;
}

View File

@@ -108,3 +108,5 @@ 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);
int dns_name_apply_idna(const char *name, char **ret);

View File

@@ -598,6 +598,26 @@ static void test_dns_name_common_suffix(void) {
test_dns_name_common_suffix_one("FOO.BAR", "tEST.bAR", "BAR");
}
static void test_dns_name_apply_idna_one(const char *s, const char *result) {
#ifdef HAVE_LIBIDN
_cleanup_free_ char *buf = NULL;
assert_se(dns_name_apply_idna(s, &buf) >= 0);
assert_se(dns_name_equal(buf, result) > 0);
#endif
}
static void test_dns_name_apply_idna(void) {
test_dns_name_apply_idna_one("", "");
test_dns_name_apply_idna_one("foo", "foo");
test_dns_name_apply_idna_one("foo.", "foo");
test_dns_name_apply_idna_one("foo.bar", "foo.bar");
test_dns_name_apply_idna_one("foo.bar.", "foo.bar");
test_dns_name_apply_idna_one("föö", "xn--f-1gaa");
test_dns_name_apply_idna_one("föö.", "xn--f-1gaa");
test_dns_name_apply_idna_one("föö.bär", "xn--f-1gaa.xn--br-via");
test_dns_name_apply_idna_one("föö.bär.", "xn--f-1gaa.xn--br-via");
}
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@@ -624,6 +644,7 @@ int main(int argc, char *argv[]) {
test_dns_name_equal_skip();
test_dns_name_compare_func();
test_dns_name_common_suffix();
test_dns_name_apply_idna();
return 0;
}