]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-question.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-question.c
index 3249448d3b73ba145badcabd850c41c6beba8002..4dc3de40526472533d7e4b6a99ae8c42c6c5fd10 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 "resolved-dns-question.h"
 
 DnsQuestion *dns_question_new(unsigned n) {
@@ -107,7 +107,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,8 +116,15 @@ 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++) {
-                r = dns_resource_key_match_cname(q->keys[i], rr, search_domain);
+                /* 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;
         }
@@ -139,23 +146,28 @@ int dns_question_is_valid_for_query(DnsQuestion *q) {
         if (q->n_keys > 65535)
                 return 0;
 
-        name = DNS_RESOURCE_KEY_NAME(q->keys[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 = 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 +189,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 +216,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 +245,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 +255,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 +266,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;
 
@@ -282,11 +292,12 @@ const char *dns_question_first_name(DnsQuestion *q) {
         if (q->n_keys < 1)
                 return NULL;
 
-        return DNS_RESOURCE_KEY_NAME(q->keys[0]);
+        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 +306,20 @@ 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;
+                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;
@@ -365,13 +390,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;
+                        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)