From 4ad7f2761da661853dcc29d542efb4727abb1101 Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Tue, 2 Jun 2015 11:49:43 -0700 Subject: resolve: move dns routines into shared --- Makefile.am | 42 ++- src/resolve/resolved-bus.c | 2 +- src/resolve/resolved-dns-answer.c | 2 +- src/resolve/resolved-dns-domain.c | 613 ------------------------------------ src/resolve/resolved-dns-domain.h | 50 --- src/resolve/resolved-dns-packet.c | 2 +- src/resolve/resolved-dns-question.c | 2 +- src/resolve/resolved-dns-rr.c | 2 +- src/resolve/resolved-dns-scope.c | 2 +- src/resolve/resolved-dns-zone.c | 2 +- src/resolve/resolved-manager.c | 2 +- src/resolve/test-dns-domain.c | 192 ----------- src/shared/dns-domain.c | 613 ++++++++++++++++++++++++++++++++++++ src/shared/dns-domain.h | 50 +++ src/test/test-dns-domain.c | 192 +++++++++++ 15 files changed, 883 insertions(+), 885 deletions(-) delete mode 100644 src/resolve/resolved-dns-domain.c delete mode 100644 src/resolve/resolved-dns-domain.h delete mode 100644 src/resolve/test-dns-domain.c create mode 100644 src/shared/dns-domain.c create mode 100644 src/shared/dns-domain.h create mode 100644 src/test/test-dns-domain.c diff --git a/Makefile.am b/Makefile.am index 5d30d7d51c..55bab725f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -762,6 +762,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/udev-util.h \ src/shared/device-nodes.c \ src/shared/device-nodes.h \ + src/shared/dns-domain.c \ + src/shared/dns-domain.h \ src/shared/util.c \ src/shared/util.h \ src/shared/virt.c \ @@ -978,6 +980,7 @@ libsystemd_shared_la_CFLAGS = \ libsystemd_shared_la_LIBADD = \ $(SELINUX_LIBS) \ $(CAP_LIBS) \ + $(LIBIDN_LIBS) \ -lm # ----------------------------------------------------------------------------- @@ -1446,7 +1449,8 @@ tests += \ test-copy \ test-cap-list \ test-sigbus \ - test-verbs + test-verbs \ + test-dns-domain EXTRA_DIST += \ test/a.service \ @@ -1577,6 +1581,15 @@ test_hostname_SOURCES = \ test_hostname_LDADD = \ libsystemd-core.la +test_dns_domain_SOURCES = \ + src/test/test-dns-domain.c + +test_dns_domain_LDADD = \ + libsystemd-network.la \ + libsystemd-internal.la \ + libsystemd-shared.la \ + $(LIBIDN_LIBS) + if ENABLE_EFI manual_tests += \ test-boot-timestamp @@ -3382,7 +3395,8 @@ test_dhcp_client_SOURCES = \ test_dhcp_client_LDADD = \ libsystemd-network.la \ libsystemd-internal.la \ - libsystemd-shared.la + libsystemd-shared.la \ + $(LIBIDN_LIBS) test_dhcp_server_SOURCES = \ src/libsystemd-network/test-dhcp-server.c @@ -5405,8 +5419,6 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-link.h \ src/resolve/resolved-link.c \ src/resolve/resolved-def.h \ - src/resolve/resolved-dns-domain.h \ - src/resolve/resolved-dns-domain.c \ src/resolve/resolved-dns-rr.h \ src/resolve/resolved-dns-rr.c \ src/resolve/resolved-dns-question.h \ @@ -5476,20 +5488,6 @@ GENERAL_ALIASES += \ nodist_pkgsysconf_DATA += \ src/resolve/resolved.conf -tests += \ - test-dns-domain - -test_dns_domain_SOURCES = \ - src/resolve/resolved-dns-domain.h \ - src/resolve/resolved-dns-domain.c \ - src/resolve/test-dns-domain.c - -test_dns_domain_LDADD = \ - libsystemd-network.la \ - libsystemd-internal.la \ - libsystemd-shared.la \ - $(LIBIDN_LIBS) - libnss_resolve_la_SOURCES = \ src/nss-resolve/nss-resolve.sym \ src/nss-resolve/nss-resolve.c @@ -5520,8 +5518,6 @@ systemd_resolve_host_SOURCES = \ src/resolve/resolved-dns-answer.h \ src/resolve/resolved-dns-question.c \ src/resolve/resolved-dns-question.h \ - src/resolve/resolved-dns-domain.c \ - src/resolve/resolved-dns-domain.h \ src/resolve/dns-type.c \ src/resolve/dns-type.h @@ -5551,7 +5547,8 @@ systemd_networkd_SOURCES = \ src/network/networkd.c systemd_networkd_LDADD = \ - libsystemd-networkd-core.la + libsystemd-networkd-core.la \ + $(LIBIDN_LIBS) if HAVE_LIBIPTC systemd_networkd_LDADD += \ @@ -5650,7 +5647,8 @@ test_network_SOURCES = \ src/network/test-network.c test_network_LDADD = \ - libsystemd-networkd-core.la + libsystemd-networkd-core.la \ + $(LIBIDN_LIBS) if HAVE_LIBIPTC test_network_LDADD += \ diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 171141e3a4..6db12511f9 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -22,7 +22,7 @@ #include "bus-common-errors.h" #include "bus-util.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-bus.h" #include "resolved-def.h" diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index e08eb667cc..f77b98e505 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -20,7 +20,7 @@ ***/ #include "resolved-dns-answer.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" DnsAnswer *dns_answer_new(unsigned n) { DnsAnswer *a; diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c deleted file mode 100644 index e1eb3ddfe5..0000000000 --- a/src/resolve/resolved-dns-domain.c +++ /dev/null @@ -1,613 +0,0 @@ -/*-*- 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 . - ***/ - -#ifdef HAVE_LIBIDN -#include -#include -#endif - -#include "resolved-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; -} diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h deleted file mode 100644 index 516d244f7a..0000000000 --- a/src/resolve/resolved-dns-domain.h +++ /dev/null @@ -1,50 +0,0 @@ -/*-*- 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 . - ***/ - -#pragma once - - -#include "hashmap.h" -#include "in-addr-util.h" - -#define DNS_LABEL_MAX 63 -#define DNS_NAME_MAX 255 - -int dns_label_unescape(const char **name, char *dest, size_t sz); -int dns_label_escape(const char *p, size_t l, char **ret); - -int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); -int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); - -int dns_name_normalize(const char *s, char **_ret); - -unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]); -int dns_name_compare_func(const void *a, const void *b); -extern const struct hash_ops dns_name_hash_ops; - -int dns_name_equal(const char *x, const char *y); -int dns_name_endswith(const char *name, const char *suffix); - -int dns_name_reverse(int family, const union in_addr_union *a, char **ret); -int dns_name_address(const char *p, int *family, union in_addr_union *a); - -int dns_name_root(const char *name); -int dns_name_single_label(const char *name); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 21756f566f..bb74b1828e 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -23,7 +23,7 @@ #include "util.h" #include "strv.h" #include "unaligned.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-dns-packet.h" int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 45bcbbf23a..4d71f5e3d4 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -20,7 +20,7 @@ ***/ #include "resolved-dns-question.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" DnsQuestion *dns_question_new(unsigned n) { DnsQuestion *q; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 78d9e4a412..c1818eef9c 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -23,7 +23,7 @@ #include "strv.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-dns-rr.h" #include "resolved-dns-packet.h" #include "dns-type.h" diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 7369cbf50f..c25ac2216d 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -27,7 +27,7 @@ #include "af-list.h" #include "random-util.h" #include "hostname-util.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-dns-scope.h" #define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index a4c9b7d7af..32d771a954 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -22,7 +22,7 @@ #include "list.h" #include "resolved-dns-zone.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-dns-packet.h" /* Never allow more than 1K entries */ diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 7fc2803ddb..f8d4db7aad 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -34,7 +34,7 @@ #include "random-util.h" #include "hostname-util.h" -#include "resolved-dns-domain.h" +#include "dns-domain.h" #include "resolved-conf.h" #include "resolved-bus.h" #include "resolved-manager.h" diff --git a/src/resolve/test-dns-domain.c b/src/resolve/test-dns-domain.c deleted file mode 100644 index c3208abc78..0000000000 --- a/src/resolve/test-dns-domain.c +++ /dev/null @@ -1,192 +0,0 @@ -/*-*- 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 . - ***/ - -#include "macro.h" -#include "resolved-dns-domain.h" - -static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret) { - char buffer[buffer_sz]; - int r; - - r = dns_label_unescape(&what, buffer, buffer_sz); - assert_se(r == ret); - - if (r < 0) - return; - - assert_se(streq(buffer, expect)); -} - -static void test_dns_label_unescape(void) { - test_dns_label_unescape_one("hallo", "hallo", 6, 5); - test_dns_label_unescape_one("hallo", "hallo", 4, -ENOSPC); - test_dns_label_unescape_one("", "", 10, 0); - test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12); - test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5); - test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL); - test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL); - test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7); - test_dns_label_unescape_one(".", "", 20, 0); - test_dns_label_unescape_one("..", "", 20, -EINVAL); - test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); - test_dns_label_unescape_one("foobar.", "foobar", 20, 6); -} - -static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { - _cleanup_free_ char *t = NULL; - int r; - - r = dns_label_escape(what, l, &t); - assert_se(r == ret); - - if (r < 0) - return; - - assert_se(streq_ptr(expect, t)); -} - -static void test_dns_label_escape(void) { - test_dns_label_escape_one("", 0, "", 0); - test_dns_label_escape_one("hallo", 5, "hallo", 5); - test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); - test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); -} - -static void test_dns_name_normalize_one(const char *what, const char *expect, int ret) { - _cleanup_free_ char *t = NULL; - int r; - - r = dns_name_normalize(what, &t); - assert_se(r == ret); - - if (r < 0) - return; - - assert_se(streq_ptr(expect, t)); -} - -static void test_dns_name_normalize(void) { - test_dns_name_normalize_one("", "", 0); - test_dns_name_normalize_one("f", "f", 0); - test_dns_name_normalize_one("f.waldi", "f.waldi", 0); - test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); - test_dns_name_normalize_one("\\000", NULL, -EINVAL); - test_dns_name_normalize_one("..", NULL, -EINVAL); - test_dns_name_normalize_one(".foobar", NULL, -EINVAL); - test_dns_name_normalize_one("foobar.", "foobar", 0); - test_dns_name_normalize_one(".", "", 0); -} - -static void test_dns_name_equal_one(const char *a, const char *b, int ret) { - int r; - - r = dns_name_equal(a, b); - assert_se(r == ret); - - r = dns_name_equal(b, a); - assert_se(r == ret); -} - -static void test_dns_name_equal(void) { - test_dns_name_equal_one("", "", true); - test_dns_name_equal_one("x", "x", true); - test_dns_name_equal_one("x", "x.", true); - test_dns_name_equal_one("abc.def", "abc.def", true); - test_dns_name_equal_one("abc.def", "ABC.def", true); - test_dns_name_equal_one("abc.def", "CBA.def", false); - test_dns_name_equal_one("", "xxx", false); - test_dns_name_equal_one("ab", "a", false); - test_dns_name_equal_one("\\000", "xxxx", -EINVAL); - test_dns_name_equal_one(".", "", true); - test_dns_name_equal_one(".", ".", true); - test_dns_name_equal_one("..", "..", -EINVAL); -} - -static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { - assert_se(dns_name_endswith(a, b) == ret); -} - -static void test_dns_name_endswith(void) { - test_dns_name_endswith_one("", "", true); - test_dns_name_endswith_one("", "xxx", false); - test_dns_name_endswith_one("xxx", "", true); - test_dns_name_endswith_one("x", "x", true); - test_dns_name_endswith_one("x", "y", false); - test_dns_name_endswith_one("x.y", "y", true); - test_dns_name_endswith_one("x.y", "Y", true); - test_dns_name_endswith_one("x.y", "x", false); - test_dns_name_endswith_one("x.y.z", "Z", true); - test_dns_name_endswith_one("x.y.z", "y.Z", true); - test_dns_name_endswith_one("x.y.z", "x.y.Z", true); - test_dns_name_endswith_one("x.y.z", "waldo", false); - test_dns_name_endswith_one("x.y.z.u.v.w", "y.z", false); - test_dns_name_endswith_one("x.y.z.u.v.w", "u.v.w", true); - test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); -} - -static void test_dns_name_root(void) { - assert_se(dns_name_root("") == true); - assert_se(dns_name_root(".") == true); - assert_se(dns_name_root("xxx") == false); - assert_se(dns_name_root("xxx.") == false); - assert_se(dns_name_root("..") == -EINVAL); -} - -static void test_dns_name_single_label(void) { - assert_se(dns_name_single_label("") == false); - assert_se(dns_name_single_label(".") == false); - assert_se(dns_name_single_label("..") == -EINVAL); - assert_se(dns_name_single_label("x") == true); - assert_se(dns_name_single_label("x.") == true); - assert_se(dns_name_single_label("xx.yy") == false); -} - -static void test_dns_name_reverse_one(const char *address, const char *name) { - _cleanup_free_ char *p = NULL; - union in_addr_union a, b = {}; - int familya, familyb; - - assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); - assert_se(dns_name_reverse(familya, &a, &p) >= 0); - assert_se(streq(p, name)); - assert_se(dns_name_address(p, &familyb, &b) > 0); - assert_se(familya == familyb); - assert_se(in_addr_equal(familya, &a, &b)); -} - -static void test_dns_name_reverse(void) { - test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); - test_dns_name_reverse_one("fe80::47", "7.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa"); -} - -int main(int argc, char *argv[]) { - - test_dns_label_unescape(); - test_dns_label_escape(); - test_dns_name_normalize(); - test_dns_name_equal(); - test_dns_name_endswith(); - test_dns_name_root(); - test_dns_name_single_label(); - test_dns_name_reverse(); - - return 0; -} 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 . + ***/ + +#ifdef HAVE_LIBIDN +#include +#include +#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; +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h new file mode 100644 index 0000000000..516d244f7a --- /dev/null +++ b/src/shared/dns-domain.h @@ -0,0 +1,50 @@ +/*-*- 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 . + ***/ + +#pragma once + + +#include "hashmap.h" +#include "in-addr-util.h" + +#define DNS_LABEL_MAX 63 +#define DNS_NAME_MAX 255 + +int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char **ret); + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); + +int dns_name_normalize(const char *s, char **_ret); + +unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]); +int dns_name_compare_func(const void *a, const void *b); +extern const struct hash_ops dns_name_hash_ops; + +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); + +int dns_name_root(const char *name); +int dns_name_single_label(const char *name); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c new file mode 100644 index 0000000000..527cdd3b54 --- /dev/null +++ b/src/test/test-dns-domain.c @@ -0,0 +1,192 @@ +/*-*- 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 . + ***/ + +#include "macro.h" +#include "dns-domain.h" + +static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret) { + char buffer[buffer_sz]; + int r; + + r = dns_label_unescape(&what, buffer, buffer_sz); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq(buffer, expect)); +} + +static void test_dns_label_unescape(void) { + test_dns_label_unescape_one("hallo", "hallo", 6, 5); + test_dns_label_unescape_one("hallo", "hallo", 4, -ENOSPC); + test_dns_label_unescape_one("", "", 10, 0); + test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12); + test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5); + test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7); + test_dns_label_unescape_one(".", "", 20, 0); + test_dns_label_unescape_one("..", "", 20, -EINVAL); + test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); + test_dns_label_unescape_one("foobar.", "foobar", 20, 6); +} + +static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_label_escape(what, l, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_label_escape(void) { + test_dns_label_escape_one("", 0, "", 0); + test_dns_label_escape_one("hallo", 5, "hallo", 5); + test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); +} + +static void test_dns_name_normalize_one(const char *what, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_name_normalize(what, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_name_normalize(void) { + test_dns_name_normalize_one("", "", 0); + test_dns_name_normalize_one("f", "f", 0); + test_dns_name_normalize_one("f.waldi", "f.waldi", 0); + test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); + test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("..", NULL, -EINVAL); + test_dns_name_normalize_one(".foobar", NULL, -EINVAL); + test_dns_name_normalize_one("foobar.", "foobar", 0); + test_dns_name_normalize_one(".", "", 0); +} + +static void test_dns_name_equal_one(const char *a, const char *b, int ret) { + int r; + + r = dns_name_equal(a, b); + assert_se(r == ret); + + r = dns_name_equal(b, a); + assert_se(r == ret); +} + +static void test_dns_name_equal(void) { + test_dns_name_equal_one("", "", true); + test_dns_name_equal_one("x", "x", true); + test_dns_name_equal_one("x", "x.", true); + test_dns_name_equal_one("abc.def", "abc.def", true); + test_dns_name_equal_one("abc.def", "ABC.def", true); + test_dns_name_equal_one("abc.def", "CBA.def", false); + test_dns_name_equal_one("", "xxx", false); + test_dns_name_equal_one("ab", "a", false); + test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one(".", "", true); + test_dns_name_equal_one(".", ".", true); + test_dns_name_equal_one("..", "..", -EINVAL); +} + +static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_endswith(a, b) == ret); +} + +static void test_dns_name_endswith(void) { + test_dns_name_endswith_one("", "", true); + test_dns_name_endswith_one("", "xxx", false); + test_dns_name_endswith_one("xxx", "", true); + test_dns_name_endswith_one("x", "x", true); + test_dns_name_endswith_one("x", "y", false); + test_dns_name_endswith_one("x.y", "y", true); + test_dns_name_endswith_one("x.y", "Y", true); + test_dns_name_endswith_one("x.y", "x", false); + test_dns_name_endswith_one("x.y.z", "Z", true); + test_dns_name_endswith_one("x.y.z", "y.Z", true); + test_dns_name_endswith_one("x.y.z", "x.y.Z", true); + test_dns_name_endswith_one("x.y.z", "waldo", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "y.z", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "u.v.w", true); + test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); +} + +static void test_dns_name_root(void) { + assert_se(dns_name_root("") == true); + assert_se(dns_name_root(".") == true); + assert_se(dns_name_root("xxx") == false); + assert_se(dns_name_root("xxx.") == false); + assert_se(dns_name_root("..") == -EINVAL); +} + +static void test_dns_name_single_label(void) { + assert_se(dns_name_single_label("") == false); + assert_se(dns_name_single_label(".") == false); + assert_se(dns_name_single_label("..") == -EINVAL); + assert_se(dns_name_single_label("x") == true); + assert_se(dns_name_single_label("x.") == true); + assert_se(dns_name_single_label("xx.yy") == false); +} + +static void test_dns_name_reverse_one(const char *address, const char *name) { + _cleanup_free_ char *p = NULL; + union in_addr_union a, b = {}; + int familya, familyb; + + assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); + assert_se(dns_name_reverse(familya, &a, &p) >= 0); + assert_se(streq(p, name)); + assert_se(dns_name_address(p, &familyb, &b) > 0); + assert_se(familya == familyb); + assert_se(in_addr_equal(familya, &a, &b)); +} + +static void test_dns_name_reverse(void) { + test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); + test_dns_name_reverse_one("fe80::47", "7.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa"); +} + +int main(int argc, char *argv[]) { + + test_dns_label_unescape(); + test_dns_label_escape(); + test_dns_name_normalize(); + test_dns_name_equal(); + test_dns_name_endswith(); + test_dns_name_root(); + test_dns_name_single_label(); + test_dns_name_reverse(); + + return 0; +} -- cgit v1.2.3-54-g00ecf From df6cfeeff7b66856dd20956efe97d87faf8ce1c3 Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Tue, 2 Jun 2015 16:29:39 -0700 Subject: shared: add convenience function for validating dns names --- src/shared/dns-domain.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 516d244f7a..00caf5d700 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -35,6 +35,15 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); int dns_name_normalize(const char *s, char **_ret); +static inline int dns_name_is_valid(const char *s) { + int r; + r = dns_name_normalize(s, NULL); + if (r == -EINVAL) + return 0; + if (r < 0) + return r; + return 1; +} unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]); int dns_name_compare_func(const void *a, const void *b); -- cgit v1.2.3-54-g00ecf From 37de250906222211d2bf1755c41191a06d2126dd Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Tue, 2 Jun 2015 16:30:42 -0700 Subject: libsystemd-network: use domain validation instead of hostname validation for dhcp domain option previously hostname_is_valid was used to validate domain names, which would silently drop perfectly valid dns names that were longer than a single dns label. --- src/libsystemd-network/sd-dhcp-lease.c | 12 +++++++++++- src/network/networkd-network.c | 16 +++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 8a4220621b..d8bc76edda 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -32,6 +32,7 @@ #include "dhcp-lease-internal.h" #include "sd-dhcp-lease.h" #include "network-internal.h" +#include "dns-domain.h" int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); @@ -504,9 +505,18 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option, if (e) *e = 0; - if (!hostname_is_valid(domainname) || is_localhost(domainname)) + if (is_localhost(domainname)) break; + r = dns_name_is_valid(domainname); + if (r <= 0) { + if (r < 0) + log_error_errno(r, "Failed to validate domain name: %s: %m", domainname); + if (r == 0) + log_warning("Domain name is not valid, ignoring: %s", domainname); + break; + } + free(lease->domainname); lease->domainname = domainname; domainname = NULL; diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 5947084106..6f32e5f4a4 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -30,6 +30,7 @@ #include "networkd-netdev.h" #include "networkd-link.h" #include "network-internal.h" +#include "dns-domain.h" static int network_load_one(Manager *manager, const char *filename) { _cleanup_network_free_ Network *network = NULL; @@ -466,11 +467,16 @@ int config_parse_domains(const char *unit, STRV_FOREACH(domain, *domains) { if (is_localhost(*domain)) log_syntax(unit, LOG_ERR, filename, line, EINVAL, "'localhost' domain names may not be configured, ignoring assignment: %s", *domain); - else if (!hostname_is_valid(*domain)) { - if (!streq(*domain, "*")) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, "domain name is not valid, ignoring assignment: %s", *domain); - } else - continue; + else { + r = dns_name_is_valid(*domain); + if (r <= 0 && !streq(*domain, "*")) { + if (r < 0) + log_error_errno(r, "Failed to validate domain name: %s: %m", *domain); + if (r == 0) + log_warning("Domain name is not valid, ignoring assignment: %s", *domain); + } else + continue; + } strv_remove(*domains, *domain); -- cgit v1.2.3-54-g00ecf