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);