chiark / gitweb /
resolved: fix which return codes we check
[elogind.git] / src / resolve / resolved-dns-domain.c
index 27739c1..8ed1ecf 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
  ***/
 
+#ifdef HAVE_LIBIDN
+#include <idna.h>
+#include <stringprep.h>
+#endif
+
 #include "resolved-dns-domain.h"
 
 int dns_label_unescape(const char **name, char *dest, size_t sz) {
@@ -88,7 +93,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                         } else
                                 return -EINVAL;
 
-                } else if (*n >= ' ' && *n != 127) {
+                } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
 
                         /* Normal character */
                         *(d++) = *(n++);
@@ -141,13 +146,13 @@ int dns_label_escape(const char *p, size_t l, char **ret) {
 
                         /* Proper character */
                         *(q++) = *p;
-                } else if (*p >= ' ' && *p != 127) {
+                } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
 
                         /* Everything else */
                         *(q++) = '\\';
-                        *(q++) = '0' + (char) ((unsigned) *p / 100);
-                        *(q++) = '0' + (char) (((unsigned) *p / 10) % 10);
-                        *(q++) = '0' + (char) ((unsigned) *p % 10);
+                        *(q++) = '0' + (char) ((uint8_t) *p / 100);
+                        *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
+                        *(q++) = '0' + (char) ((uint8_t) *p % 10);
 
                 } else
                         return -EINVAL;
@@ -164,6 +169,83 @@ int dns_label_escape(const char *p, size_t l, char **ret) {
         return r;
 }
 
+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;
+        const char *p;
+        bool contains_8bit = false;
+
+        assert(encoded);
+        assert(decoded);
+        assert(decoded_max >= DNS_LABEL_MAX);
+
+        if (encoded_size <= 0)
+                return 0;
+
+        for (p = encoded; p < encoded + encoded_size; p++)
+                if ((uint8_t) *p > 127)
+                        contains_8bit = true;
+
+        if (!contains_8bit)
+                return 0;
+
+        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        if (!input)
+                return -ENOMEM;
+
+        if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
+                return -EINVAL;
+
+        return strlen(decoded);
+#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;
+        uint32_t *output = NULL;
+        size_t w;
+
+        /* To be invoked after unescaping */
+
+        assert(encoded);
+        assert(decoded);
+
+        if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
+                return 0;
+
+        if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
+                return 0;
+
+        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        if (!input)
+                return -ENOMEM;
+
+        output_size = input_size;
+        output = newa(uint32_t, output_size);
+
+        idna_to_unicode_44i(input, input_size, output, &output_size, 0);
+
+        result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
+        if (!result)
+                return -ENOMEM;
+        if (w <= 0)
+                return 0;
+        if (w+1 > decoded_max)
+                return -EINVAL;
+
+        memcpy(decoded, result, w+1);
+        return w;
+#else
+        return 0;
+#endif
+}
+
 int dns_name_normalize(const char *s, char **_ret) {
         _cleanup_free_ char *ret = NULL;
         size_t n = 0, allocated = 0;
@@ -172,11 +254,11 @@ int dns_name_normalize(const char *s, char **_ret) {
         int r;
 
         assert(s);
-        assert(_ret);
 
         for (;;) {
                 _cleanup_free_ char *t = NULL;
                 char label[DNS_LABEL_MAX];
+                int k;
 
                 r = dns_label_unescape(&p, label, sizeof(label));
                 if (r < 0)
@@ -187,6 +269,12 @@ int dns_name_normalize(const char *s, char **_ret) {
                         break;
                 }
 
+                k = dns_label_undo_idna(label, r, label, sizeof(label));
+                if (k < 0)
+                        return k;
+                if (k > 0)
+                        r = k;
+
                 r = dns_label_escape(label, r, &t);
                 if (r < 0)
                         return r;
@@ -203,34 +291,47 @@ int dns_name_normalize(const char *s, char **_ret) {
                 n += r;
         }
 
+        if (n > DNS_NAME_MAX)
+                return -EINVAL;
+
         if (!GREEDY_REALLOC(ret, allocated, n + 1))
                 return -ENOMEM;
 
         ret[n] = 0;
-        *_ret = ret;
-        ret = NULL;
+
+        if (_ret) {
+                *_ret = ret;
+                ret = NULL;
+        }
 
         return 0;
 }
 
 unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
         const char *p = s;
-        unsigned long ul = 0;
+        unsigned long ul = hash_key[0];
         int r;
 
         assert(p);
 
         while (*p) {
                 char label[DNS_LABEL_MAX+1];
+                int k;
 
                 r = dns_label_unescape(&p, label, sizeof(label));
                 if (r < 0)
                         break;
 
+                k = dns_label_undo_idna(label, r, label, sizeof(label));
+                if (k < 0)
+                        break;
+                if (k > 0)
+                        r = k;
+
                 label[r] = 0;
                 ascii_strlower(label);
 
-                ul = hash_key[0] * ul + ul + string_hash_func(label, hash_key);
+                ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
         }
 
         return ul;
@@ -238,7 +339,7 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_
 
 int dns_name_compare_func(const void *a, const void *b) {
         const char *x = a, *y = b;
-        int r, q;
+        int r, q, k, w;
 
         assert(a);
         assert(b);
@@ -254,6 +355,15 @@ int dns_name_compare_func(const void *a, const void *b) {
                 if (r < 0 || q < 0)
                         return r - q;
 
+                k = dns_label_undo_idna(la, r, la, sizeof(la));
+                w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
+                if (k < 0 || w < 0)
+                        return k - w;
+                if (k > 0)
+                        r = k;
+                if (w > 0)
+                        r = w;
+
                 la[r] = lb[q] = 0;
                 r = strcasecmp(la, lb);
                 if (r != 0)
@@ -262,7 +372,7 @@ int dns_name_compare_func(const void *a, const void *b) {
 }
 
 int dns_name_equal(const char *x, const char *y) {
-        int r, q;
+        int r, q, k, w;
 
         assert(x);
         assert(y);
@@ -277,9 +387,20 @@ int dns_name_equal(const char *x, const char *y) {
                 if (r < 0)
                         return r;
 
+                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;
+                w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
+                if (w < 0)
+                        return w;
+                if (w > 0)
+                        q = w;
 
                 la[r] = lb[q] = 0;
                 if (strcasecmp(la, lb))
@@ -289,7 +410,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;
+        int r, q, k, w;
 
         assert(name);
         assert(suffix);
@@ -303,13 +424,23 @@ int dns_name_endswith(const char *name, const char *suffix) {
                 r = dns_label_unescape(&n, ln, sizeof(ln));
                 if (r < 0)
                         return r;
+                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;
 
                 q = dns_label_unescape(&s, ls, sizeof(ls));
-                if (r < 0)
-                        return r;
+                if (q < 0)
+                        return q;
+                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;
@@ -357,6 +488,93 @@ int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
         return 0;
 }
 
+int dns_name_address(const char *p, int *family, union in_addr_union *address) {
+        int r;
+
+        assert(p);
+        assert(family);
+        assert(address);
+
+        r = dns_name_endswith(p, "in-addr.arpa");
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                uint8_t a[4];
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(a); i++) {
+                        char label[DNS_LABEL_MAX+1];
+
+                        r = dns_label_unescape(&p, label, sizeof(label));
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                return -EINVAL;
+                        if (r > 3)
+                                return -EINVAL;
+
+                        r = safe_atou8(label, &a[i]);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = dns_name_equal(p, "in-addr.arpa");
+                if (r <= 0)
+                        return r;
+
+                *family = AF_INET;
+                address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
+                                             ((uint32_t) a[2] << 16) |
+                                             ((uint32_t) a[1] << 8) |
+                                              (uint32_t) a[0]);
+
+                return 1;
+        }
+
+        r = dns_name_endswith(p, "ip6.arpa");
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                struct in6_addr a;
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
+                        char label[DNS_LABEL_MAX+1];
+                        int x, y;
+
+                        r = dns_label_unescape(&p, label, sizeof(label));
+                        if (r <= 0)
+                                return r;
+                        if (r != 1)
+                                return -EINVAL;
+                        x = unhexchar(label[0]);
+                        if (x < 0)
+                                return -EINVAL;
+
+                        r = dns_label_unescape(&p, label, sizeof(label));
+                        if (r <= 0)
+                                return r;
+                        if (r != 1)
+                                return -EINVAL;
+                        y = unhexchar(label[0]);
+                        if (y < 0)
+                                return -EINVAL;
+
+                        a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
+                }
+
+                r = dns_name_equal(p, "ip6.arpa");
+                if (r <= 0)
+                        return r;
+
+                *family = AF_INET6;
+                address->in6 = a;
+                return 1;
+        }
+
+        return 0;
+}
+
 int dns_name_root(const char *name) {
         char label[DNS_LABEL_MAX+1];
         int r;
@@ -379,7 +597,6 @@ int dns_name_single_label(const char *name) {
         r = dns_label_unescape(&name, label, sizeof(label));
         if (r < 0)
                 return r;
-
         if (r == 0)
                 return 0;