]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/dns-domain.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / shared / dns-domain.c
index d36eb5056f07d2f9167e6f7e75c319c51e63a335..670aa48800104faea3689f2285e05468b3bcb1e7 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.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
  ***/
 
-#ifdef HAVE_LIBIDN
-#include <idna.h>
-#include <stringprep.h>
+#if HAVE_LIBIDN2
+#  include <idn2.h>
+#elif HAVE_LIBIDN
+#  include <idna.h>
+#  include <stringprep.h>
 #endif
 
 #include <endian.h>
@@ -76,7 +77,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                                 /* Ending NUL */
                                 return -EINVAL;
 
-                        else if (*n == '\\' || *n == '.') {
+                        else if (IN_SET(*n, '\\', '.')) {
                                 /* Escaped backslash or dot */
 
                                 if (d)
@@ -133,6 +134,10 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
         if (r == 0 && *n)
                 return -EINVAL;
 
+        /* More than one trailing dot? */
+        if (*n == '.')
+                return -EINVAL;
+
         if (sz >= 1 && d)
                 *d = 0;
 
@@ -160,7 +165,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
         }
 
         terminal = *label_terminal;
-        assert(*terminal == '.' || *terminal == 0);
+        assert(IN_SET(*terminal, 0, '.'));
 
         /* Skip current terminal character (and accept domain names ending it ".") */
         if (*terminal == 0)
@@ -182,7 +187,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
                         unsigned slashes = 0;
 
                         for (y = terminal - 1; y >= name && *y == '\\'; y--)
-                                slashes ++;
+                                slashes++;
 
                         if (slashes % 2 == 0) {
                                 /* The '.' was not escaped */
@@ -194,7 +199,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
                         }
                 }
 
-                terminal --;
+                terminal--;
         }
 
         r = dns_label_unescape(&name, dest, sz);
@@ -224,7 +229,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         q = dest;
         while (l > 0) {
 
-                if (*p == '.' || *p == '\\') {
+                if (IN_SET(*p, '.', '\\')) {
 
                         /* Dot or backslash */
 
@@ -236,8 +241,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
 
                         sz -= 2;
 
-                } else if (*p == '_' ||
-                           *p == '-' ||
+                } else if (IN_SET(*p, '_', '-') ||
                            (*p >= '0' && *p <= '9') ||
                            (*p >= 'a' && *p <= 'z') ||
                            (*p >= 'A' && *p <= 'Z')) {
@@ -297,8 +301,8 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
         return r;
 }
 
+#if HAVE_LIBIDN
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-#ifdef HAVE_LIBIDN
         _cleanup_free_ uint32_t *input = NULL;
         size_t input_size, l;
         const char *p;
@@ -333,7 +337,7 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
 
         l = strlen(buffer);
 
-        /* Verify that the the result is not longer than one DNS label. */
+        /* Verify that the result is not longer than one DNS label. */
         if (l <= 0 || l > DNS_LABEL_MAX)
                 return -EINVAL;
         if (l > decoded_max)
@@ -346,13 +350,9 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
                 decoded[l] = 0;
 
         return (int) l;
-#else
-        return 0;
-#endif
 }
 
 int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-#ifdef HAVE_LIBIDN
         size_t input_size, output_size;
         _cleanup_free_ uint32_t *input = NULL;
         _cleanup_free_ char *result = NULL;
@@ -397,23 +397,26 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
                 decoded[w] = 0;
 
         return w;
-#else
-        return 0;
-#endif
 }
+#endif
 
 int dns_name_concat(const char *a, const char *b, char **_ret) {
         _cleanup_free_ char *ret = NULL;
         size_t n = 0, allocated = 0;
-        const char *p = a;
+        const char *p;
         bool first = true;
         int r;
 
-        assert(a);
+        if (a)
+                p = a;
+        else if (b) {
+                p = b;
+                b = NULL;
+        } else
+                goto finish;
 
         for (;;) {
                 char label[DNS_LABEL_MAX];
-                int k;
 
                 r = dns_label_unescape(&p, label, sizeof(label));
                 if (r < 0)
@@ -432,12 +435,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;
@@ -464,12 +461,21 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
                 n += r;
         }
 
+finish:
         if (n > DNS_HOSTNAME_MAX)
                 return -EINVAL;
 
         if (_ret) {
-                if (!GREEDY_REALLOC(ret, allocated, n + 1))
-                        return -ENOMEM;
+                if (n == 0) {
+                        /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */
+                        if (!GREEDY_REALLOC(ret, allocated, 2))
+                                return -ENOMEM;
+
+                        ret[n++] = '.';
+                } else {
+                        if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                                return -ENOMEM;
+                }
 
                 ret[n] = 0;
                 *_ret = ret;
@@ -487,7 +493,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 +500,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 +511,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 +530,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;
@@ -559,7 +542,7 @@ const struct hash_ops dns_name_hash_ops = {
 };
 
 int dns_name_equal(const char *x, const char *y) {
-        int r, q, k, w;
+        int r, q;
 
         assert(x);
         assert(y);
@@ -570,24 +553,10 @@ int dns_name_equal(const char *x, const char *y) {
                 r = dns_label_unescape(&x, la, sizeof(la));
                 if (r < 0)
                         return r;
-                if (r > 0) {
-                        k = dns_label_undo_idna(la, r, la, sizeof(la));
-                        if (k < 0)
-                                return k;
-                        if (k > 0)
-                                r = k;
-                }
 
                 q = dns_label_unescape(&y, lb, sizeof(lb));
                 if (q < 0)
                         return q;
-                if (q > 0) {
-                        w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
-                        if (w < 0)
-                                return w;
-                        if (w > 0)
-                                q = w;
-                }
 
                 if (r != q)
                         return false;
@@ -601,7 +570,7 @@ int dns_name_equal(const char *x, const char *y) {
 
 int dns_name_endswith(const char *name, const char *suffix) {
         const char *n, *s, *saved_n = NULL;
-        int r, q, k, w;
+        int r, q;
 
         assert(name);
         assert(suffix);
@@ -615,13 +584,6 @@ int dns_name_endswith(const char *name, const char *suffix) {
                 r = dns_label_unescape(&n, ln, sizeof(ln));
                 if (r < 0)
                         return r;
-                if (r > 0) {
-                        k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
-                        if (k < 0)
-                                return k;
-                        if (k > 0)
-                                r = k;
-                }
 
                 if (!saved_n)
                         saved_n = n;
@@ -629,13 +591,6 @@ int dns_name_endswith(const char *name, const char *suffix) {
                 q = dns_label_unescape(&s, ls, sizeof(ls));
                 if (q < 0)
                         return q;
-                if (q > 0) {
-                        w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
-                        if (w < 0)
-                                return w;
-                        if (w > 0)
-                                q = w;
-                }
 
                 if (r == 0 && q == 0)
                         return true;
@@ -652,24 +607,6 @@ int dns_name_endswith(const char *name, const char *suffix) {
         }
 }
 
-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_startswith(const char *name, const char *prefix) {
         const char *n, *p;
         int r, q;
@@ -683,13 +620,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;
 
@@ -702,7 +639,7 @@ int dns_name_startswith(const char *name, const char *prefix) {
 
 int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
         const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
-        int r, q, k, w;
+        int r, q;
 
         assert(name);
         assert(old_suffix);
@@ -721,13 +658,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 r = dns_label_unescape(&n, ln, sizeof(ln));
                 if (r < 0)
                         return r;
-                if (r > 0) {
-                        k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
-                        if (k < 0)
-                                return k;
-                        if (k > 0)
-                                r = k;
-                }
 
                 if (!saved_after)
                         saved_after = n;
@@ -735,13 +665,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 q = dns_label_unescape(&s, ls, sizeof(ls));
                 if (q < 0)
                         return q;
-                if (q > 0) {
-                        w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
-                        if (w < 0)
-                                return w;
-                        if (w > 0)
-                                q = w;
-                }
 
                 if (r == 0 && q == 0)
                         break;
@@ -1143,17 +1066,15 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
                 if (x >= 3 && srv_type_label_is_valid(c, cn)) {
 
                         if (dns_service_name_label_is_valid(a, an)) {
-
                                 /* OK, got <name> . <type> . <type2> . <domain> */
 
                                 name = strndup(a, an);
                                 if (!name)
                                         return -ENOMEM;
 
-                                type = new(char, bn+1+cn+1);
+                                type = strjoin(b, ".", c);
                                 if (!type)
                                         return -ENOMEM;
-                                strcpy(stpcpy(stpcpy(type, b), "."), c);
 
                                 d = p;
                                 goto finish;
@@ -1165,10 +1086,9 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
 
                         name = NULL;
 
-                        type = new(char, an+1+bn+1);
+                        type = strjoin(a, ".", b);
                         if (!type)
                                 return -ENOMEM;
-                        strcpy(stpcpy(stpcpy(type, a), "."), b);
 
                         d = q;
                         goto finish;
@@ -1202,22 +1122,20 @@ finish:
         return 0;
 }
 
-int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
-        const char* labels[DNS_N_LABELS_MAX+1];
-        unsigned n = 0;
+static int dns_name_build_suffix_table(const char *name, const char*table[]) {
         const char *p;
+        unsigned n = 0;
         int r;
 
         assert(name);
-        assert(ret);
+        assert(table);
 
         p = name;
         for (;;) {
                 if (n > DNS_N_LABELS_MAX)
                         return -EINVAL;
 
-                labels[n] = p;
-
+                table[n] = p;
                 r = dns_name_parent(&p);
                 if (r < 0)
                         return r;
@@ -1227,13 +1145,47 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
                 n++;
         }
 
-        if (n < n_labels)
+        return (int) n;
+}
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
+        const char* labels[DNS_N_LABELS_MAX+1];
+        int n;
+
+        assert(name);
+        assert(ret);
+
+        n = dns_name_build_suffix_table(name, labels);
+        if (n < 0)
+                return n;
+
+        if ((unsigned) n < n_labels)
                 return -EINVAL;
 
         *ret = labels[n - n_labels];
         return (int) (n - n_labels);
 }
 
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
+        int r;
+
+        assert(a);
+        assert(ret);
+
+        for (; n_labels > 0; n_labels--) {
+                r = dns_name_parent(&a);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        *ret = "";
+                        return 0;
+                }
+        }
+
+        *ret = a;
+        return 1;
+}
+
 int dns_name_count_labels(const char *name) {
         unsigned n = 0;
         const char *p;
@@ -1264,14 +1216,165 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
         assert(a);
         assert(b);
 
-        while (n_labels > 0) {
+        r = dns_name_skip(a, n_labels, &a);
+        if (r <= 0)
+                return r;
 
-                r = dns_name_parent(&a);
-                if (r <= 0)
+        return dns_name_equal(a, b);
+}
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
+        const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
+        int n = 0, m = 0, k = 0, r, q;
+
+        assert(a);
+        assert(b);
+        assert(ret);
+
+        /* Determines the common suffix of domain names a and b */
+
+        n = dns_name_build_suffix_table(a, a_labels);
+        if (n < 0)
+                return n;
+
+        m = dns_name_build_suffix_table(b, b_labels);
+        if (m < 0)
+                return m;
+
+        for (;;) {
+                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+                const char *x, *y;
+
+                if (k >= n || k >= m) {
+                        *ret = a_labels[n - k];
+                        return 0;
+                }
+
+                x = a_labels[n - 1 - k];
+                r = dns_label_unescape(&x, la, sizeof(la));
+                if (r < 0)
                         return r;
 
-                n_labels --;
+                y = b_labels[m - 1 - k];
+                q = dns_label_unescape(&y, lb, sizeof(lb));
+                if (q < 0)
+                        return q;
+
+                if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
+                        *ret = a_labels[n - k];
+                        return 0;
+                }
+
+                k++;
         }
+}
 
-        return dns_name_equal(a, b);
+int dns_name_apply_idna(const char *name, char **ret) {
+        /* Return negative on error, 0 if not implemented, positive on success. */
+
+#if HAVE_LIBIDN2
+        int r;
+        _cleanup_free_ char *t = NULL;
+
+        assert(name);
+        assert(ret);
+
+        r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
+                           IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+        log_debug("idn2_lookup_u8: %s → %s", name, t);
+        if (r == IDN2_OK) {
+                if (!startswith(name, "xn--")) {
+                        _cleanup_free_ char *s = NULL;
+
+                        r = idn2_to_unicode_8z8z(t, &s, 0);
+                        if (r != IDN2_OK) {
+                                log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s",
+                                          t, r, idn2_strerror(r));
+                                return 0;
+                        }
+
+                        if (!streq_ptr(name, s)) {
+                                log_debug("idn2 roundtrip failed: \"%s\" → \"%s\" → \"%s\", ignoring.",
+                                          name, t, s);
+                                return 0;
+                        }
+                }
+
+                *ret = t;
+                t = NULL;
+                return 1; /* *ret has been written */
+        }
+
+        log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r));
+        if (r == IDN2_2HYPHEN)
+                /* The name has two hypens — forbidden by IDNA2008 in some cases */
+                return 0;
+        if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL))
+                return -ENOSPC;
+        return -EINVAL;
+#elif HAVE_LIBIDN
+        _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 1;
+#else
+        return 0;
+#endif
+}
+
+int dns_name_is_valid_or_address(const char *name) {
+        /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */
+
+        if (isempty(name))
+                return 0;
+
+        if (in_addr_from_string_auto(name, NULL, NULL) >= 0)
+                return 1;
+
+        return dns_name_is_valid(name);
 }