]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-query.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-query.c
index a00851658e4661544c263fffe4628c4dfa4ae5b7..227d0b5d11d00aecf33917cc00af6e6c4b9566ea 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -21,6 +20,7 @@
 
 #include "alloc-util.h"
 #include "dns-domain.h"
+#include "dns-type.h"
 #include "hostname-util.h"
 #include "local-addresses.h"
 #include "resolved-dns-query.h"
@@ -29,7 +29,7 @@
 #include "string-util.h"
 
 /* How long to wait for the query in total */
-#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
+#define QUERY_TIMEOUT_USEC (60 * USEC_PER_SEC)
 
 #define CNAME_MAX 8
 #define QUERIES_MAX 2048
@@ -63,6 +63,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) {
 
         while ((t = set_steal_first(c->transactions))) {
                 set_remove(t->notify_query_candidates, c);
+                set_remove(t->notify_query_candidates_done, c);
                 dns_transaction_gc(t);
         }
 }
@@ -83,9 +84,7 @@ DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) {
         if (c->scope)
                 LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c);
 
-        free(c);
-
-        return NULL;
+        return mfree(c);
 }
 
 static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
@@ -140,6 +139,10 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
         if (r < 0)
                 goto gc;
 
+        r = set_ensure_allocated(&t->notify_query_candidates_done, NULL);
+        if (r < 0)
+                goto gc;
+
         r = set_put(t->notify_query_candidates, c);
         if (r < 0)
                 goto gc;
@@ -150,6 +153,7 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
                 goto gc;
         }
 
+        t->clamp_ttl = c->query->clamp_ttl;
         return 1;
 
 gc:
@@ -161,6 +165,7 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
         DnsTransaction *t;
         Iterator i;
         int r;
+        unsigned n = 0;
 
         assert(c);
 
@@ -172,8 +177,14 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
                 r = dns_transaction_go(t);
                 if (r < 0)
                         return r;
+
+                n++;
         }
 
+        /* If there was nothing to start, then let's proceed immediately */
+        if (n == 0)
+                dns_query_candidate_notify(c);
+
         return 0;
 }
 
@@ -222,6 +233,31 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
         return state;
 }
 
+static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) {
+        int family;
+
+        assert(c);
+
+        /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of
+         * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR,
+         * or a routable IPv6 address if we query an AAAA RR. */
+
+        if (!c->query->suppress_unroutable_family)
+                return true;
+
+        if (c->scope->protocol != DNS_PROTOCOL_DNS)
+                return true;
+
+        family = dns_type_to_af(type);
+        if (family < 0)
+                return true;
+
+        if (c->scope->link)
+                return link_relevant(c->scope->link, family, false);
+        else
+                return manager_routable(c->scope->manager, family);
+}
+
 static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
         DnsQuestion *question;
         DnsResourceKey *key;
@@ -236,14 +272,24 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
         /* Create one transaction per question key */
         DNS_QUESTION_FOREACH(key, question) {
                 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL;
+                DnsResourceKey *qkey;
+
+                if (!dns_query_candidate_is_routable(c, key->type))
+                        continue;
 
                 if (c->search_domain) {
                         r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name);
                         if (r < 0)
                                 goto fail;
-                }
 
-                r = dns_query_candidate_add_transaction(c, new_key ?: key);
+                        qkey = new_key;
+                } else
+                        qkey = key;
+
+                if (!dns_scope_good_key(c->scope, qkey))
+                        continue;
+
+                r = dns_query_candidate_add_transaction(c, qkey);
                 if (r < 0)
                         goto fail;
 
@@ -357,6 +403,17 @@ DnsQuery *dns_query_free(DnsQuery *q) {
         sd_bus_message_unref(q->request);
         sd_bus_track_unref(q->bus_track);
 
+        dns_packet_unref(q->request_dns_packet);
+        dns_packet_unref(q->reply_dns_packet);
+
+        if (q->request_dns_stream) {
+                /* Detach the stream from our query, in case something else keeps a reference to it. */
+                q->request_dns_stream->complete = NULL;
+                q->request_dns_stream->on_packet = NULL;
+                q->request_dns_stream->query = NULL;
+                dns_stream_unref(q->request_dns_stream);
+        }
+
         free(q->request_address_string);
 
         if (q->manager) {
@@ -364,9 +421,7 @@ DnsQuery *dns_query_free(DnsQuery *q) {
                 q->manager->n_dns_queries--;
         }
 
-        free(q);
-
-        return NULL;
+        return mfree(q);
 }
 
 int dns_query_new(
@@ -374,12 +429,14 @@ int dns_query_new(
                 DnsQuery **ret,
                 DnsQuestion *question_utf8,
                 DnsQuestion *question_idna,
-                int ifindex, uint64_t flags) {
+                int ifindex,
+                uint64_t flags) {
 
         _cleanup_(dns_query_freep) DnsQuery *q = NULL;
         DnsResourceKey *key;
         bool good = false;
         int r;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
         assert(m);
 
@@ -430,31 +487,20 @@ int dns_query_new(
         q->answer_family = AF_UNSPEC;
 
         /* First dump UTF8  question */
-        DNS_QUESTION_FOREACH(key, question_utf8) {
-                _cleanup_free_ char *p = NULL;
-
-                r = dns_resource_key_to_string(key, &p);
-                if (r < 0)
-                        return r;
-
-                log_debug("Looking up RR for %s.", strstrip(p));
-        }
+        DNS_QUESTION_FOREACH(key, question_utf8)
+                log_debug("Looking up RR for %s.",
+                          dns_resource_key_to_string(key, key_str, sizeof key_str));
 
         /* 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));
+                log_debug("Looking up IDNA RR for %s.",
+                          dns_resource_key_to_string(key, key_str, sizeof key_str));
         }
 
         LIST_PREPEND(queries, m->dns_queries, q);
@@ -472,7 +518,7 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
         assert(q);
         assert(auxiliary_for);
 
-        /* Ensure that that the query is not auxiliary yet, and
+        /* Ensure that the query is not auxiliary yet, and
          * nothing else is auxiliary to it either */
         assert(!q->auxiliary_for);
         assert(!q->auxiliary_queries);
@@ -578,7 +624,18 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
                         q->question_utf8,
                         q->ifindex,
                         &answer);
+        if (r == -ENXIO) {
+                /* If we get ENXIO this tells us to generate NXDOMAIN unconditionally. */
 
+                dns_query_reset_answer(q);
+                q->answer_rcode = DNS_RCODE_NXDOMAIN;
+                q->answer_protocol = dns_synthesize_protocol(q->flags);
+                q->answer_family = dns_synthesize_family(q->flags);
+                q->answer_authenticated = true;
+                *state = DNS_TRANSACTION_RCODE_FAILURE;
+
+                return 0;
+        }
         if (r <= 0)
                 return r;
 
@@ -766,6 +823,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                 q->answer = dns_answer_unref(q->answer);
                 q->answer_rcode = 0;
                 q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+                q->answer_authenticated = false;
                 q->answer_errno = c->error_code;
         }
 
@@ -774,7 +832,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                 switch (t->state) {
 
                 case DNS_TRANSACTION_SUCCESS: {
-                        /* We found a successfuly reply, merge it into the answer */
+                        /* We found a successfully reply, merge it into the answer */
                         r = dns_answer_extend(&q->answer, t->answer);
                         if (r < 0)
                                 goto fail;
@@ -802,15 +860,18 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                         continue;
 
                 default:
-                        /* Any kind of failure? Store the data away,
-                         * if there's nothing stored yet. */
-
+                        /* Any kind of failure? Store the data away, if there's nothing stored yet. */
                         if (state == DNS_TRANSACTION_SUCCESS)
                                 continue;
 
+                        /* If there's already an authenticated negative reply stored, then prefer that over any unauthenticated one */
+                        if (q->answer_authenticated && !t->answer_authenticated)
+                                continue;
+
                         q->answer = dns_answer_unref(q->answer);
                         q->answer_rcode = t->answer_rcode;
                         q->answer_dnssec_result = t->answer_dnssec_result;
+                        q->answer_authenticated = t->answer_authenticated;
                         q->answer_errno = t->answer_errno;
 
                         state = t->state;
@@ -896,7 +957,7 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
 
         assert(q);
 
-        q->n_cname_redirects ++;
+        q->n_cname_redirects++;
         if (q->n_cname_redirects > CNAME_MAX)
                 return -ELOOP;
 
@@ -924,6 +985,17 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
         if (r == 0 && k == 0) /* No actual cname happened? */
                 return -ELOOP;
 
+        if (q->answer_protocol == DNS_PROTOCOL_DNS) {
+                /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources
+                 * cannot invade the local namespace. The opposite way we permit: local names may redirect to global
+                 * ones. */
+
+                q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */
+        }
+
+        /* Turn off searching for the new name */
+        q->flags |= SD_RESOLVED_NO_SEARCH;
+
         dns_question_unref(q->question_idna);
         q->question_idna = nq_idna;
         nq_idna = NULL;
@@ -934,10 +1006,8 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
 
         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;
+        q->state = DNS_TRANSACTION_NULL;
 
         return 0;
 }
@@ -975,6 +1045,9 @@ int dns_query_process_cname(DnsQuery *q) {
         if (q->flags & SD_RESOLVED_NO_CNAME)
                 return -ELOOP;
 
+        if (!q->answer_authenticated)
+                q->previous_redirect_unauthenticated = true;
+
         /* OK, let's actually follow the CNAME */
         r = dns_query_cname_redirect(q, cname);
         if (r < 0)
@@ -1062,3 +1135,9 @@ const char *dns_query_string(DnsQuery *q) {
 
         return dns_question_first_name(q->question_idna);
 }
+
+bool dns_query_fully_authenticated(DnsQuery *q) {
+        assert(q);
+
+        return q->answer_authenticated && !q->previous_redirect_unauthenticated;
+}