]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/dns-domain.c
Merge pull request #1668 from ssahani/net1
[thirdparty/systemd.git] / src / shared / dns-domain.c
index 20a44ce4e11bf4ba84ffd6446ad193c095cae370..d4df9d2acbaeff724530d6906e7d8451b354803d 100644 (file)
@@ -24,6 +24,7 @@
 #include <stringprep.h>
 #endif
 
+#include "string-util.h"
 #include "dns-domain.h"
 
 int dns_label_unescape(const char **name, char *dest, size_t sz) {
@@ -114,6 +115,68 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
         return r;
 }
 
+/* @label_terminal: terminal character of a label, updated to point to the terminal character of
+ *                  the previous label (always skipping one dot) or to NULL if there are no more
+ *                  labels. */
+int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
+        const char *terminal;
+        int r;
+
+        assert(name);
+        assert(label_terminal);
+        assert(dest);
+
+        /* no more labels */
+        if (!*label_terminal) {
+                if (sz >= 1)
+                        *dest = 0;
+
+                return 0;
+        }
+
+        assert(**label_terminal == '.' || **label_terminal == 0);
+
+        /* skip current terminal character */
+        terminal = *label_terminal - 1;
+
+        /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
+        for (;;) {
+                if (terminal < name) {
+                        /* reached the first label, so indicate that there are no more */
+                        terminal = NULL;
+                        break;
+                }
+
+                /* find the start of the last label */
+                if (*terminal == '.') {
+                        const char *y;
+                        unsigned slashes = 0;
+
+                        for (y = terminal - 1; y >= name && *y == '\\'; y--)
+                                slashes ++;
+
+                        if (slashes % 2 == 0) {
+                                /* the '.' was not escaped */
+                                name = terminal + 1;
+                                break;
+                        } else {
+                                terminal = y;
+                                continue;
+                        }
+                }
+
+                terminal --;
+        }
+
+        r = dns_label_unescape(&name, dest, sz);
+        if (r < 0)
+                return r;
+
+        *label_terminal = terminal;
+
+        return r;
+}
+
 int dns_label_escape(const char *p, size_t l, char **ret) {
         _cleanup_free_ char *s = NULL;
         char *q;
@@ -246,14 +309,14 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
 #endif
 }
 
-int dns_name_normalize(const char *s, char **_ret) {
+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 = s;
+        const char *p = a;
         bool first = true;
         int r;
 
-        assert(s);
+        assert(a);
 
         for (;;) {
                 _cleanup_free_ char *t = NULL;
@@ -266,6 +329,14 @@ int dns_name_normalize(const char *s, char **_ret) {
                 if (r == 0) {
                         if (*p != 0)
                                 return -EINVAL;
+
+                        if (b) {
+                                /* Now continue with the second string, if there is one */
+                                p = b;
+                                b = NULL;
+                                continue;
+                        }
+
                         break;
                 }
 
@@ -279,27 +350,29 @@ int dns_name_normalize(const char *s, char **_ret) {
                 if (r < 0)
                         return r;
 
-                if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
-                        return -ENOMEM;
+                if (_ret) {
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
+                                return -ENOMEM;
 
-                if (!first)
-                        ret[n++] = '.';
-                else
-                        first = false;
+                        if (!first)
+                                ret[n++] = '.';
+                        else
+                                first = false;
+
+                        memcpy(ret + n, t, r);
+                }
 
-                memcpy(ret + n, t, r);
                 n += r;
         }
 
         if (n > DNS_NAME_MAX)
                 return -EINVAL;
 
-        if (!GREEDY_REALLOC(ret, allocated, n + 1))
-                return -ENOMEM;
-
-        ret[n] = 0;
-
         if (_ret) {
+                if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                        return -ENOMEM;
+
+                ret[n] = 0;
                 *_ret = ret;
                 ret = NULL;
         }
@@ -307,9 +380,8 @@ int dns_name_normalize(const char *s, char **_ret) {
         return 0;
 }
 
-unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
+void dns_name_hash_func(const void *s, struct siphash *state) {
         const char *p = s;
-        unsigned long ul = hash_key[0];
         int r;
 
         assert(p);
@@ -328,30 +400,37 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_
                 if (k > 0)
                         r = k;
 
+                if (r == 0)
+                        break;
+
                 label[r] = 0;
                 ascii_strlower(label);
 
-                ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
+                string_hash_func(label, state);
         }
 
-        return ul;
+        /* enforce that all names are terminated by the empty label */
+        string_hash_func("", state);
 }
 
 int dns_name_compare_func(const void *a, const void *b) {
-        const char *x = a, *y = b;
+        const char *x, *y;
         int r, q, k, w;
 
         assert(a);
         assert(b);
 
+        x = (const char *) a + strlen(a);
+        y = (const char *) b + strlen(b);
+
         for (;;) {
                 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
 
-                if (*x == 0 && *y == 0)
+                if (x == NULL && y == NULL)
                         return 0;
 
-                r = dns_label_unescape(&x, la, sizeof(la));
-                q = dns_label_unescape(&y, lb, sizeof(lb));
+                r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
+                q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
                 if (r < 0 || q < 0)
                         return r - q;
 
@@ -464,6 +543,28 @@ int dns_name_endswith(const char *name, const char *suffix) {
         }
 }
 
+int dns_name_between(const char *a, const char *b, const char *c) {
+        int n;
+
+        /* Determine if b is strictly greater than a and strictly smaller than c.
+           We consider the order of names to be circular, so that if a is
+           strictly greater than c, we consider b to be between them if it is
+           either greater than a or smaller than c. This is how the canonical
+           DNS name order used in NSEC records work. */
+
+        n = dns_name_compare_func(a, c);
+        if (n == 0)
+                return -EINVAL;
+        else if (n < 0)
+                /*       a<---b--->c       */
+                return dns_name_compare_func(a, b) < 0 &&
+                       dns_name_compare_func(b, c) < 0;
+        else
+                /* <--b--c         a--b--> */
+                return dns_name_compare_func(b, c) < 0 ||
+                       dns_name_compare_func(a, b) < 0;
+}
+
 int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
         const uint8_t *p;
         int r;