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 1/2] 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 1284d14e52..e6b573587d 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 d7477510a9..427190aa87 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 f59f3faf38..a934fe85c9 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 14a20530d4..2067dfe604 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 4e99b25e63..e2e3b7bb4c 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 91312bd2a3..3223915da6 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 c8b502d1cd..af29f73164 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 9db8b8f616..cc9b26d4d5 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 3d7074af11..090b2fac23 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 33debadb15..40aec3a1ea 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 03f160369c..fca025def0 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 a7cd8e4b51..d86add94db 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); From a8a2a0ed64f727c82e3cfc7e84f54e3e70e7a111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 May 2017 22:09:45 -0400 Subject: [PATCH 2/2] mkosi: switch over to libidn2 --- .mkosi/mkosi.arch | 2 +- .mkosi/mkosi.debian | 2 +- .mkosi/mkosi.fedora | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.mkosi/mkosi.arch b/.mkosi/mkosi.arch index b2e5e14a92..b4a50f4bc8 100644 --- a/.mkosi/mkosi.arch +++ b/.mkosi/mkosi.arch @@ -48,7 +48,7 @@ BuildPackages= kmod libcap libgcrypt - libidn + libidn2 libmicrohttpd libseccomp libtool diff --git a/.mkosi/mkosi.debian b/.mkosi/mkosi.debian index 468a6d8654..fa2aa1182d 100644 --- a/.mkosi/mkosi.debian +++ b/.mkosi/mkosi.debian @@ -53,7 +53,7 @@ BuildPackages= libfdisk-dev libgcrypt20-dev libgnutls28-dev - libidn11-dev + libidn2-dev libkmod-dev liblzma-dev liblz4-dev diff --git a/.mkosi/mkosi.fedora b/.mkosi/mkosi.fedora index 00129a5af3..d36d167d94 100644 --- a/.mkosi/mkosi.fedora +++ b/.mkosi/mkosi.fedora @@ -53,7 +53,7 @@ BuildPackages= libcap-devel libcurl-devel libgcrypt-devel - libidn-devel + libidn2-devel libmicrohttpd-devel libmount-devel libseccomp-devel