summaryrefslogtreecommitdiff
path: root/src/shared/dns-domain.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/dns-domain.c')
-rw-r--r--src/shared/dns-domain.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
new file mode 100644
index 0000000000..20a44ce4e1
--- /dev/null
+++ b/src/shared/dns-domain.c
@@ -0,0 +1,613 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+#ifdef HAVE_LIBIDN
+#include <idna.h>
+#include <stringprep.h>
+#endif
+
+#include "dns-domain.h"
+
+int dns_label_unescape(const char **name, char *dest, size_t sz) {
+ const char *n;
+ char *d;
+ int r = 0;
+
+ assert(name);
+ assert(*name);
+ assert(dest);
+
+ n = *name;
+ d = dest;
+
+ for (;;) {
+ if (*n == '.') {
+ n++;
+ break;
+ }
+
+ if (*n == 0)
+ break;
+
+ if (sz <= 0)
+ return -ENOSPC;
+
+ if (r >= DNS_LABEL_MAX)
+ return -EINVAL;
+
+ if (*n == '\\') {
+ /* Escaped character */
+
+ n++;
+
+ if (*n == 0)
+ /* Ending NUL */
+ return -EINVAL;
+
+ else if (*n == '\\' || *n == '.') {
+ /* Escaped backslash or dot */
+ *(d++) = *(n++);
+ sz--;
+ r++;
+
+ } else if (n[0] >= '0' && n[0] <= '9') {
+ unsigned k;
+
+ /* Escaped literal ASCII character */
+
+ if (!(n[1] >= '0' && n[1] <= '9') ||
+ !(n[2] >= '0' && n[2] <= '9'))
+ return -EINVAL;
+
+ k = ((unsigned) (n[0] - '0') * 100) +
+ ((unsigned) (n[1] - '0') * 10) +
+ ((unsigned) (n[2] - '0'));
+
+ /* Don't allow CC characters or anything that doesn't fit in 8bit */
+ if (k < ' ' || k > 255 || k == 127)
+ return -EINVAL;
+
+ *(d++) = (char) k;
+ sz--;
+ r++;
+
+ n += 3;
+ } else
+ return -EINVAL;
+
+ } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
+
+ /* Normal character */
+ *(d++) = *(n++);
+ sz--;
+ r++;
+ } else
+ return -EINVAL;
+ }
+
+ /* Empty label that is not at the end? */
+ if (r == 0 && *n)
+ return -EINVAL;
+
+ if (sz >= 1)
+ *d = 0;
+
+ *name = n;
+ return r;
+}
+
+int dns_label_escape(const char *p, size_t l, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ char *q;
+ int r;
+
+ assert(p);
+ assert(ret);
+
+ if (l > DNS_LABEL_MAX)
+ return -EINVAL;
+
+ s = malloc(l * 4 + 1);
+ if (!s)
+ return -ENOMEM;
+
+ q = s;
+ while (l > 0) {
+
+ if (*p == '.' || *p == '\\') {
+
+ /* Dot or backslash */
+ *(q++) = '\\';
+ *(q++) = *p;
+
+ } else if (*p == '_' ||
+ *p == '-' ||
+ (*p >= '0' && *p <= '9') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z')) {
+
+ /* Proper character */
+ *(q++) = *p;
+ } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
+
+ /* Everything else */
+ *(q++) = '\\';
+ *(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;
+
+ p++;
+ l--;
+ }
+
+ *q = 0;
+ *ret = s;
+ r = q - s;
+ s = NULL;
+
+ 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;
+ const char *p = s;
+ bool first = true;
+ int r;
+
+ assert(s);
+
+ for (;;) {
+ _cleanup_free_ char *t = NULL;
+ char label[DNS_LABEL_MAX];
+ int k;
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (*p != 0)
+ return -EINVAL;
+ 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;
+
+ if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
+ return -ENOMEM;
+
+ if (!first)
+ ret[n++] = '.';
+ else
+ first = false;
+
+ 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) {
+ *_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 = 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 = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
+ }
+
+ return ul;
+}
+
+int dns_name_compare_func(const void *a, const void *b) {
+ const char *x = a, *y = b;
+ int r, q, k, w;
+
+ assert(a);
+ assert(b);
+
+ for (;;) {
+ char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
+
+ if (*x == 0 && *y == 0)
+ return 0;
+
+ r = dns_label_unescape(&x, la, sizeof(la));
+ q = dns_label_unescape(&y, lb, sizeof(lb));
+ 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)
+ return r;
+ }
+}
+
+const struct hash_ops dns_name_hash_ops = {
+ .hash = dns_name_hash_func,
+ .compare = dns_name_compare_func
+};
+
+int dns_name_equal(const char *x, const char *y) {
+ int r, q, k, w;
+
+ assert(x);
+ assert(y);
+
+ for (;;) {
+ char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
+
+ if (*x == 0 && *y == 0)
+ return true;
+
+ r = dns_label_unescape(&x, la, sizeof(la));
+ 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))
+ return false;
+ }
+}
+
+int dns_name_endswith(const char *name, const char *suffix) {
+ const char *n, *s, *saved_n = NULL;
+ int r, q, k, w;
+
+ assert(name);
+ assert(suffix);
+
+ n = name;
+ s = suffix;
+
+ for (;;) {
+ char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
+
+ 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 (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;
+ if (r == 0 && saved_n == n)
+ return false;
+
+ ln[r] = ls[q] = 0;
+
+ if (r != q || strcasecmp(ln, ls)) {
+
+ /* Not the same, let's jump back, and try with the next label again */
+ s = suffix;
+ n = saved_n;
+ saved_n = NULL;
+ }
+ }
+}
+
+int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
+ const uint8_t *p;
+ int r;
+
+ assert(a);
+ assert(ret);
+
+ p = (const uint8_t*) a;
+
+ if (family == AF_INET)
+ r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
+ else if (family == AF_INET6)
+ r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa",
+ hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
+ hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
+ hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
+ hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
+ hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
+ hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
+ hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
+ hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
+ else
+ return -EAFNOSUPPORT;
+ if (r < 0)
+ return -ENOMEM;
+
+ 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;
+
+ assert(name);
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return r;
+
+ return r == 0 && *name == 0;
+}
+
+int dns_name_single_label(const char *name) {
+ char label[DNS_LABEL_MAX+1];
+ int r;
+
+ assert(name);
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return r;
+
+ return r == 0 && *name == 0;
+}