From 4917e7c727e0e9687f25dd76f18a56edc40a36c1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Oct 2020 11:52:41 +0200 Subject: [PATCH] shared: make libidn/libdidn2 dependency a dlopen() one --- src/resolve/meson.build | 2 +- src/resolve/resolved-manager.c | 58 ++++++++++++---------- src/shared/dns-domain.c | 53 +++++++++++++------- src/shared/idn-util.c | 91 ++++++++++++++++++++++++++++++++++ src/shared/idn-util.h | 32 ++++++++++++ src/shared/meson.build | 3 +- 6 files changed, 193 insertions(+), 46 deletions(-) create mode 100644 src/shared/idn-util.c create mode 100644 src/shared/idn-util.h diff --git a/src/resolve/meson.build b/src/resolve/meson.build index da2256f5cc..a145117efd 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -142,7 +142,7 @@ libsystemd_resolve_core = static_library( systemd_resolved_sources += [resolved_gperf_c, resolved_dnssd_gperf_c] -systemd_resolved_dependencies = [threads, libgpg_error, libm, libidn] +systemd_resolved_dependencies = [threads, libgpg_error, libm] if conf.get('ENABLE_DNS_OVER_TLS') == 1 if conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1 systemd_resolved_sources += files('resolved-dnstls-gnutls.c', diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 7523c65e96..908454d756 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -8,10 +8,6 @@ #include #include -#if HAVE_LIBIDN2 -#include -#endif - #include "af-list.h" #include "alloc-util.h" #include "bus-polkit.h" @@ -20,6 +16,7 @@ #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" +#include "idn-util.h" #include "io-util.h" #include "missing_network.h" #include "netlink-util.h" @@ -346,29 +343,38 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Couldn't find a single label in hostname."); -#if HAVE_LIBIDN2 - r = idn2_to_unicode_8z8z(label, &utf8, 0); - if (r != IDN2_OK) - return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), - "Failed to undo IDNA: %s", idn2_strerror(r)); - assert(utf8_is_valid(utf8)); - - r = strlen(utf8); - decoded = utf8; -#elif 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) - r = k; - - if (!utf8_is_valid(label)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "System hostname is not UTF-8 clean."); - decoded = label; -#else - decoded = label; /* no decoding */ +#if HAVE_LIBIDN || HAVE_LIBIDN2 + r = dlopen_idn(); + if (r < 0) { + log_debug_errno(r, "Failed to initialize IDN support, ignoring: %m"); + decoded = label; /* no decoding */ + } else #endif + { +#if HAVE_LIBIDN2 + r = sym_idn2_to_unicode_8z8z(label, &utf8, 0); + if (r != IDN2_OK) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Failed to undo IDNA: %s", sym_idn2_strerror(r)); + assert(utf8_is_valid(utf8)); + + r = strlen(utf8); + decoded = utf8; +#elif 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) + r = k; + + if (!utf8_is_valid(label)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "System hostname is not UTF-8 clean."); + decoded = label; +#else + decoded = label; /* no decoding */ +#endif + } r = dns_label_escape_new(decoded, r, &n); if (r < 0) diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 00e12e681f..35d2eaa9f1 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -1,12 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ -#if HAVE_LIBIDN2 -# include -#elif HAVE_LIBIDN -# include -# include -#endif - #include #include #include @@ -17,6 +10,7 @@ #include "hashmap.h" #include "hexdecoct.h" #include "hostname-util.h" +#include "idn-util.h" #include "in-addr-util.h" #include "macro.h" #include "parse-util.h" @@ -312,12 +306,17 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded const char *p; bool contains_8bit = false; char buffer[DNS_LABEL_MAX+1]; + int r; assert(encoded); assert(decoded); /* Converts an U-label into an A-label */ + r = dlopen_idn(); + if (r < 0) + return r; + if (encoded_size <= 0) return -EINVAL; @@ -332,11 +331,11 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded return 0; } - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + input = sym_stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); if (!input) return -ENOMEM; - if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) + if (sym_idna_to_ascii_4i(input, input_size, buffer, 0) != 0) return -EINVAL; l = strlen(buffer); @@ -362,28 +361,33 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, _cleanup_free_ char *result = NULL; uint32_t *output = NULL; size_t w; + int r; /* To be invoked after unescaping. Converts an A-label into an U-label. */ assert(encoded); assert(decoded); + r = dlopen_idn(); + if (r < 0) + return r; + if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) return -EINVAL; if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX)) return 0; - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + input = sym_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); + sym_idna_to_unicode_44i(input, input_size, output, &output_size, 0); - result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); + result = sym_stringprep_ucs4_to_utf8(output, output_size, NULL, &w); if (!result) return -ENOMEM; if (w <= 0) @@ -1266,26 +1270,38 @@ 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 HAVE_LIBIDN2 +#if HAVE_LIBIDN2 || HAVE_LIBIDN2 int r; + + r = dlopen_idn(); + if (r == EOPNOTSUPP) { + *ret = NULL; + return 0; + } + if (r < 0) + return r; +#endif + +#if HAVE_LIBIDN2 _cleanup_free_ char *t = NULL; assert(name); assert(ret); - r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, - IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, + IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); log_debug("idn2_lookup_u8: %s → %s", name, t); if (r == IDN2_OK) { if (!startswith(name, "xn--")) { _cleanup_free_ char *s = NULL; - r = idn2_to_unicode_8z8z(t, &s, 0); + r = sym_idn2_to_unicode_8z8z(t, &s, 0); if (r != IDN2_OK) { log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s", - t, r, idn2_strerror(r)); + t, r, sym_idn2_strerror(r)); return 0; } @@ -1301,7 +1317,7 @@ int dns_name_apply_idna(const char *name, char **ret) { return 1; /* *ret has been written */ } - log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r)); + log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, sym_idn2_strerror(r)); if (r == IDN2_2HYPHEN) /* The name has two hyphens — forbidden by IDNA2008 in some cases */ return 0; @@ -1358,6 +1374,7 @@ int dns_name_apply_idna(const char *name, char **ret) { return 1; #else + *ret = NULL; return 0; #endif } diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c new file mode 100644 index 0000000000..75d815df38 --- /dev/null +++ b/src/shared/idn-util.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#if HAVE_LIBIDN2 +# include +#elif HAVE_LIBIDN +# include +# include +#endif + +#include "alloc-util.h" +#include "dlfcn-util.h" +#include "idn-util.h" + +#if HAVE_LIBIDN || HAVE_LIBIDN2 +static void* idn_dl = NULL; +#endif + +#if HAVE_LIBIDN2 +int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags) = NULL; +const char *(*sym_idn2_strerror)(int rc) = NULL; +int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags) = NULL; + +int dlopen_idn(void) { + _cleanup_(dlclosep) void *dl = NULL; + int r; + + if (idn_dl) + return 0; /* Already loaded */ + + dl = dlopen("libidn2.so.0", RTLD_LAZY); + if (!dl) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libidn2 support is not installed: %s", dlerror()); + + r = dlsym_many_and_warn( + dl, + LOG_DEBUG, + &sym_idn2_lookup_u8, "idn2_lookup_u8", + &sym_idn2_strerror, "idn2_strerror", + &sym_idn2_to_unicode_8z8z, "idn2_to_unicode_8z8z", + NULL); + if (r < 0) + return r; + + /* Note that we never release the reference here, because there's no real reason to, after all this + * was traditionally a regular shared library dependency which lives forever too. */ + idn_dl = TAKE_PTR(dl); + + return 1; +} +#endif + +#if HAVE_LIBIDN +int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); +int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags); +char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); +uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); + +int dlopen_idn(void) { + _cleanup_(dlclosep) void *dl = NULL; + int r; + + if (idn_dl) + return 0; /* Already loaded */ + + dl = dlopen("libidn.so.12", RTLD_LAZY); + if (!dl) { + /* libidn broke ABI in 1.34, but not in a way we care about (a new field got added to an + * open-coded struct we do not use), hence support both versions. */ + dl = dlopen("libidn.so.11", RTLD_LAZY); + if (!dl) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libidn support is not installed: %s", dlerror()); + } + + r = dlsym_many_and_warn( + dl, + LOG_DEBUG, + &sym_idna_to_ascii_4i, "idna_to_ascii_4i", + &sym_idna_to_unicode_44i, "idna_to_unicode_44i", + &sym_stringprep_ucs4_to_utf8, "stringprep_ucs4_to_utf8", + &sym_stringprep_utf8_to_ucs4, "stringprep_utf8_to_ucs4", + NULL); + if (r < 0) + return r; + + idn_dl = TAKE_PTR(dl); + + return 1; +} +#endif diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h new file mode 100644 index 0000000000..d958559ec9 --- /dev/null +++ b/src/shared/idn-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#if HAVE_LIBIDN2 +# include +#elif HAVE_LIBIDN +# include +# include +#endif + +#include + +#if HAVE_LIBIDN2 || HAVE_LIBIDN +int dlopen_idn(void); +#else +static inline int dlopen_idn(void) { + return -EOPNOTSUPP; +} +#endif + +#if HAVE_LIBIDN2 +extern int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags); +extern const char *(*sym_idn2_strerror)(int rc); +extern int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags); +#endif + +#if HAVE_LIBIDN +extern int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); +extern int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags); +extern char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); +extern uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); +#endif diff --git a/src/shared/meson.build b/src/shared/meson.build index 723e00e437..3f409584e6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -117,6 +117,8 @@ shared_sources = files(''' group-record.h id128-print.c id128-print.h + idn-util.c + idn-util.h ima-util.c ima-util.h import-util.c @@ -355,7 +357,6 @@ libshared_deps = [threads, libcap, libcrypt, libgcrypt, - libidn, libiptc, libkmod, liblz4,