From 87057e244b4d2e723dc98b2b3cef1901c155f005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 9 May 2017 21:56:34 -0400 Subject: [PATCH] resolved: support libidn2 in addition to libidn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libidn2 2.0.0 supports IDNA2008, in contrast to libidn which supports IDNA2003. https://bugzilla.redhat.com/show_bug.cgi?id=1449145 From that bug report: Internationalized domain names exist for quite some time (IDNA2003), although the protocols describing them have evolved in an incompatible way (IDNA2008). These incompatibilities will prevent applications written for IDNA2003 to access certain problematic domain names defined with IDNA2008, e.g., faß.de is translated to domain xn--fa-hia.de with IDNA2008, while in IDNA2003 it is translated to fass.de domain. That not only causes incompatibility problems, but may be used as an attack vector to redirect users to different web sites. v2: - keep libidn support - require libidn2 >= 2.0.0 v3: - keep dns_name_apply_idna caller dumb, and keep the #ifdefs inside of the function. - use both ±IDN and ±IDN2 in the version string --- Makefile.am | 4 +++ README | 2 +- configure.ac | 33 +++++++++++++++++++------ meson.build | 21 +++++++++++++--- meson_options.txt | 2 ++ src/basic/build.h | 7 ++++++ src/resolve/resolved-dns-question.c | 8 +++--- src/resolve/resolved-manager.c | 32 ++++++++++++++++++++---- src/resolve/test-dnssec-complex.c | 2 +- src/shared/dns-domain.c | 38 ++++++++++++++++++++--------- src/shared/dns-domain.h | 2 ++ src/test/test-dns-domain.c | 2 +- 12 files changed, 119 insertions(+), 34 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1284d14e526..e6b573587dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1137,6 +1137,7 @@ libshared_la_CFLAGS = \ $(AM_CFLAGS) \ $(ACL_CFLAGS) \ $(LIBIDN_CFLAGS) \ + $(LIBIDN2_CFLAGS) \ $(SECCOMP_CFLAGS) \ $(BLKID_CFLAGS) \ $(LIBCRYPTSETUP_CFLAGS) @@ -1148,6 +1149,7 @@ libshared_la_LIBADD = \ libudev-internal.la \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ + $(LIBIDN2_LIBS) \ $(SECCOMP_LIBS) \ $(BLKID_LIBS) \ $(LIBCRYPTSETUP_LIBS) @@ -1171,6 +1173,7 @@ libsystemd_shared_la_CFLAGS = \ $(libudev_internal_la_CFLAGS) \ $(ACL_CFLAGS) \ $(LIBIDN_CFLAGS) \ + $(LIBIDN2_CFLAGS) \ $(SECCOMP_CFLAGS) \ $(BLKID_CFLAGS) \ $(LIBCRYPTSETUP_CFLAGS) \ @@ -1185,6 +1188,7 @@ libsystemd_shared_la_LIBADD = \ $(libudev_internal_la_LIBADD) \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ + $(LIBIDN2_LIBS) \ $(SECCOMP_LIBS) \ $(BLKID_LIBS) \ $(LIBCRYPTSETUP_LIBS) diff --git a/README b/README index d7477510a9c..427190aa87f 100644 --- a/README +++ b/README @@ -142,7 +142,7 @@ REQUIREMENTS: libqrencode (optional) libmicrohttpd (optional) libpython (optional) - libidn (optional) + libidn2 or libidn (optional) elfutils >= 158 (optional) make, gcc, and similar tools diff --git a/configure.ac b/configure.ac index f59f3faf384..a934fe85c98 100644 --- a/configure.ac +++ b/configure.ac @@ -1015,16 +1015,32 @@ AM_CONDITIONAL(HAVE_LIBCURL, [test "$have_libcurl" = "yes"]) AM_CONDITIONAL(HAVE_REMOTE, [test "$have_microhttpd" = "yes" -o "$have_libcurl" = "yes"]) # ------------------------------------------------------------------------------ +have_libidn2=no +AC_ARG_ENABLE(libidn2, AS_HELP_STRING([--disable-libidn2], [disable optional LIBIDN2 support])) +if test "x$enable_libidn2" != "xno"; then + PKG_CHECK_MODULES(LIBIDN2, [libidn2 >= 2.0.0], + [AC_DEFINE(HAVE_LIBIDN2, 1, [Define if libidn2 is available]) + have_libidn2=yes + M4_DEFINES="$M4_DEFINES -DHAVE_LIBIDN2"], + [have_libidn2=no]) + if test "x$have_libidn2" = "xno" -a "x$enable_libidn2" = "xyes"; then + AC_MSG_ERROR([*** libidn2 support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_LIBIDN2, [test "$have_libidn2" = "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]) +if test "$have_libidn2" != "yes"; then + 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 fi AM_CONDITIONAL(HAVE_LIBIDN, [test "$have_libidn" = "yes"]) @@ -1715,6 +1731,7 @@ AC_MSG_RESULT([ MICROHTTPD: ${have_microhttpd} GNUTLS: ${have_gnutls} libcurl: ${have_libcurl} + libidn2: ${have_libidn2} libidn: ${have_libidn} libiptc: ${have_libiptc} ELFUTILS: ${have_elfutils} diff --git a/meson.build b/meson.build index 14a20530d43..2067dfe6046 100644 --- a/meson.build +++ b/meson.build @@ -791,15 +791,29 @@ else endif want_libidn = get_option('libidn') -if want_libidn != 'false' +want_libidn2 = get_option('libidn2') +if want_libidn == 'true' and want_libidn2 == 'true' + error('libidn and libidn2 cannot be requested simultaneously') +endif + +if want_libidn2 != 'false' and want_libidn != 'true' + libidn = dependency('libidn2', + required : want_libidn2 == 'true') + # libidn is used for both libidn and libidn2 objects + if libidn.found() + conf.set('HAVE_LIBIDN2', true) + m4_defines += ['-DHAVE_LIBIDN2'] + endif +else + libidn = [] +endif +if not conf.get('HAVE_LIBIDN2', false) and want_libidn != 'false' libidn = dependency('libidn', required : want_libidn == 'true') if libidn.found() conf.set('HAVE_LIBIDN', true) m4_defines += ['-DHAVE_LIBIDN'] endif -else - libidn = [] endif want_libiptc = get_option('libiptc') @@ -2428,6 +2442,7 @@ foreach tuple : [ ['microhttpd'], ['gnutls'], ['libcurl'], + ['libidn2'], ['libidn'], ['libiptc'], ['elfutils'], diff --git a/meson_options.txt b/meson_options.txt index 4e99b25e63f..e2e3b7bb4c2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -195,6 +195,8 @@ option('libcryptsetup', type : 'combo', choices : ['auto', 'true', 'false'], description : 'libcryptsetup support') option('libcurl', type : 'combo', choices : ['auto', 'true', 'false'], description : 'libcurl support') +option('libidn2', type : 'combo', choices : ['auto', 'true', 'false'], + description : 'libidn2 support') option('libidn', type : 'combo', choices : ['auto', 'true', 'false'], description : 'libidn support') option('libiptc', type : 'combo', choices : ['auto', 'true', 'false'], diff --git a/src/basic/build.h b/src/basic/build.h index 91312bd2a3c..3223915da61 100644 --- a/src/basic/build.h +++ b/src/basic/build.h @@ -127,6 +127,12 @@ #define _KMOD_FEATURE_ "-KMOD" #endif +#ifdef HAVE_LIBIDN2 +#define _IDN2_FEATURE_ "+IDN2" +#else +#define _IDN2_FEATURE_ "-IDN2" +#endif + #ifdef HAVE_LIBIDN #define _IDN_FEATURE_ "+IDN" #else @@ -154,5 +160,6 @@ _BLKID_FEATURE_ " " \ _ELFUTILS_FEATURE_ " " \ _KMOD_FEATURE_ " " \ + _IDN2_FEATURE_ " " \ _IDN_FEATURE_ " " \ _CGROUP_HIEARCHY_ diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index c8b502d1cd7..af29f731643 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -309,8 +309,8 @@ int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bo r = dns_name_apply_idna(name, &buf); if (r < 0) return r; - - name = buf; + if (r > 0) + name = buf; } q = dns_question_new(family == AF_UNSPEC ? 2 : 1); @@ -422,8 +422,8 @@ int dns_question_new_service( r = dns_name_apply_idna(domain, &buf); if (r < 0) return r; - - domain = buf; + if (r > 0) + domain = buf; } r = dns_service_join(service, type, domain, &joined); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 9db8b8f6161..cc9b26d4d53 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -21,6 +21,10 @@ #include #include +#ifdef HAVE_LIBIDN2 +#include +#endif + #include "af-list.h" #include "alloc-util.h" #include "dirent-util.h" @@ -324,9 +328,14 @@ static int manager_network_monitor_listen(Manager *m) { static int determine_hostname(char **full_hostname, char **llmnr_hostname, char **mdns_hostname) { _cleanup_free_ char *h = NULL, *n = NULL; +#if defined(HAVE_LIBIDN2) + _cleanup_free_ char *utf8 = NULL; +#elif defined(HAVE_LIBIDN) + int k; +#endif char label[DNS_LABEL_MAX]; - const char *p; - int r, k; + const char *p, *decoded; + int r; assert(full_hostname); assert(llmnr_hostname); @@ -339,7 +348,7 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char return log_debug_errno(r, "Can't determine system hostname: %m"); p = h; - r = dns_label_unescape(&p, label, sizeof(label)); + r = dns_label_unescape(&p, label, sizeof label); if (r < 0) return log_error_errno(r, "Failed to unescape host name: %m"); if (r == 0) { @@ -347,7 +356,16 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char return -EINVAL; } - k = dns_label_undo_idna(label, r, label, sizeof(label)); +#if defined(HAVE_LIBIDN2) + r = idn2_to_unicode_8z8z(label, &utf8, 0); + if (r != IDN2_OK) + return log_error("Failed to undo IDNA: %s", idn2_strerror(r)); + assert(utf8_is_valid(utf8)); + + r = strlen(utf8); + decoded = utf8; +#elif defined(HAVE_LIBIDN) + k = dns_label_undo_idna(label, r, label, sizeof label); if (k < 0) return log_error_errno(k, "Failed to undo IDNA: %m"); if (k > 0) @@ -357,8 +375,12 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char log_error("System hostname is not UTF-8 clean."); return -EINVAL; } + decoded = label; +#else + decoded = label; /* no decoding */ +#endif - r = dns_label_escape_new(label, r, &n); + r = dns_label_escape_new(decoded, r, &n); if (r < 0) return log_error_errno(r, "Failed to escape host name: %m"); diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c index 3d7074af11f..090b2fac23b 100644 --- a/src/resolve/test-dnssec-complex.c +++ b/src/resolve/test-dnssec-complex.c @@ -218,7 +218,7 @@ int main(int argc, char* argv[]) { test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); -#ifdef HAVE_LIBIDN +#if defined(HAVE_LIBIDN2) || defined(HAVE_LIBIDN) /* Unsigned A with IDNA conversion necessary */ test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 33debadb153..40aec3a1ea7 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -17,9 +17,11 @@ along with systemd; If not, see . ***/ -#ifdef HAVE_LIBIDN -#include -#include +#if defined(HAVE_LIBIDN2) +# include +#elif defined(HAVE_LIBIDN) +# include +# include #endif #include @@ -299,8 +301,8 @@ int dns_label_escape_new(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 +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { _cleanup_free_ uint32_t *input = NULL; size_t input_size, l; const char *p; @@ -348,13 +350,9 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded decoded[l] = 0; return (int) l; -#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; @@ -399,10 +397,8 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, decoded[w] = 0; return w; -#else - return 0; -#endif } +#endif int dns_name_concat(const char *a, const char *b, char **_ret) { _cleanup_free_ char *ret = NULL; @@ -1274,6 +1270,23 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) { } int dns_name_apply_idna(const char *name, char **ret) { + /* Return negative on error, 0 if not implemented, positive on success. */ + +#if defined(HAVE_LIBIDN2) + int r; + + assert(name); + assert(ret); + + r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) ret, + IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + if (r == IDN2_OK) + return 1; /* *ret has been written */ + else if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL)) + return -ENOSPC; + else + return -EINVAL; +#elif defined(HAVE_LIBIDN) _cleanup_free_ char *buf = NULL; size_t n = 0, allocated = 0; bool first = true; @@ -1323,6 +1336,9 @@ int dns_name_apply_idna(const char *name, char **ret) { buf = NULL; return (int) n; +#else + return 0; +#endif } int dns_name_is_valid_or_address(const char *name) { diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 03f160369cb..fca025def01 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -51,8 +51,10 @@ static inline int dns_name_parent(const char **name) { return dns_label_unescape(name, NULL, DNS_LABEL_MAX); } +#if defined(HAVE_LIBIDN) 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); +#endif int dns_name_concat(const char *a, const char *b, char **ret); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index a7cd8e4b513..d86add94db5 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -608,7 +608,7 @@ static void test_dns_name_common_suffix(void) { } static void test_dns_name_apply_idna_one(const char *s, const char *result) { -#ifdef HAVE_LIBIDN +#if defined(HAVE_LIBIDN2) || defined(HAVE_LIBIDN) _cleanup_free_ char *buf = NULL; assert_se(dns_name_apply_idna(s, &buf) >= 0); assert_se(dns_name_equal(buf, result) > 0);