From bdf10b5b4d9e7abdc08bdca4b073d021b0043d1f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Jul 2014 23:43:10 +0200 Subject: [PATCH] resolved: handle IDNA domains Make sure we format UTF-8 labels as IDNA when writing them to DNS packets, and as native UTF-8 when writing them to mDNS or LLMNR packets. When comparing or processing labels always consider native UTF-8 and IDNA formats equivalent. --- Makefile.am | 9 +- configure.ac | 16 ++++ src/resolve/resolved-dns-domain.c | 132 +++++++++++++++++++++++++++++- src/resolve/resolved-dns-domain.h | 3 + src/resolve/resolved-dns-packet.c | 12 +++ 5 files changed, 166 insertions(+), 6 deletions(-) diff --git a/Makefile.am b/Makefile.am index 2d7f76d97..364e6225d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4778,7 +4778,8 @@ systemd_resolved_LDADD = \ libsystemd-label.la \ libsystemd-internal.la \ libsystemd-shared.la \ - -lm + -lm \ + $(LIBIDN_LIBS) rootlibexec_PROGRAMS += \ systemd-resolved @@ -4829,7 +4830,8 @@ test_dns_domain_LDADD = \ libsystemd-network.la \ libsystemd-label.la \ libsystemd-internal.la \ - libsystemd-shared.la + libsystemd-shared.la \ + $(LIBIDN_LIBS) libnss_resolve_la_SOURCES = \ src/nss-resolve/nss-resolve.sym \ @@ -4867,7 +4869,8 @@ systemd_resolve_host_SOURCES = \ systemd_resolve_host_LDADD = \ libsystemd-internal.la \ libsystemd-shared.la \ - -lm + -lm \ + $(LIBIDN_LIBS) rootlibexec_PROGRAMS += \ systemd-resolve-host diff --git a/configure.ac b/configure.ac index 0802cfcc9..6d5536b59 100644 --- a/configure.ac +++ b/configure.ac @@ -810,6 +810,21 @@ if test "x$enable_libcurl" != "xno"; then fi AM_CONDITIONAL(HAVE_LIBCURL, [test "$have_libcurl" = "yes"]) +# ------------------------------------------------------------------------------ +have_libidn=no +AC_ARG_ENABLE(libidn, AS_HELP_STRING([--disable-libidn], [Disable optional LIBIDN support])) +if test "x$enable_libidn" != "xno"; then + PKG_CHECK_MODULES(LIBIDN, [libidn], + [AC_DEFINE(HAVE_LIBIDN, 1, [Define if libidn is available]) + have_libidn=yes + M4_DEFINES="$M4_DEFINES -DHAVE_LIBIDN"], + [have_libidn=no]) + if test "x$have_libidn" = "xno" -a "x$enable_libidn" = "xyes"; then + AC_MSG_ERROR([*** libidn support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_LIBIDN, [test "$have_libidn" = "yes"]) + # ------------------------------------------------------------------------------ have_binfmt=no AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool])) @@ -1326,6 +1341,7 @@ AC_MSG_RESULT([ CHKCONFIG: ${have_chkconfig} GNUTLS: ${have_gnutls} libcurl: ${have_libcurl} + libidn: ${have_libidn} ELFUTILS: ${have_elfutils} binfmt: ${have_binfmt} vconsole: ${have_vconsole} diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c index 1bb011d9e..80a2b3cd3 100644 --- a/src/resolve/resolved-dns-domain.c +++ b/src/resolve/resolved-dns-domain.c @@ -19,6 +19,11 @@ 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) { @@ -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; @@ -176,6 +258,7 @@ int dns_name_normalize(const char *s, char **_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) @@ -186,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; @@ -227,11 +316,18 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_ 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) + return k; + if (k > 0) + r = k; + label[r] = 0; ascii_strlower(label); @@ -243,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); @@ -259,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) @@ -267,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); @@ -282,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)) @@ -294,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); @@ -308,6 +424,11 @@ 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; @@ -315,6 +436,11 @@ int dns_name_endswith(const char *name, const char *suffix) { q = dns_label_unescape(&s, ls, sizeof(ls)); if (r < 0) return r; + w = dns_label_undo_idna(ls, r, ls, sizeof(ls)); + if (w < 0) + return w; + if (w > 0) + q = w; if (r == 0 && q == 0) return true; diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h index 16372fd94..e674c5d9c 100644 --- a/src/resolve/resolved-dns-domain.h +++ b/src/resolve/resolved-dns-domain.h @@ -30,6 +30,9 @@ 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]); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index ba6fb3d78..ba056f1f4 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -390,6 +390,7 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { _cleanup_free_ char *s = NULL; char label[DNS_LABEL_MAX]; size_t n; + int k; n = PTR_TO_SIZE(hashmap_get(p->names, name)); if (n > 0) { @@ -414,6 +415,17 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { if (r < 0) goto fail; + if (p->protocol == DNS_PROTOCOL_DNS) + k = dns_label_apply_idna(label, r, label, sizeof(label)); + else + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) { + r = k; + goto fail; + } + if (k > 0) + r = k; + r = dns_packet_append_label(p, label, r, &n); if (r < 0) goto fail; -- 2.30.2