resolved: support libidn2 in addition to libidn

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
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2017-05-09 21:56:34 -04:00
parent 9bfc0df113
commit 87057e244b
12 changed files with 119 additions and 34 deletions

View File

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

2
README
View File

@ -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

View File

@ -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}

View File

@ -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'],

View File

@ -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'],

View File

@ -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_

View File

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

View File

@ -21,6 +21,10 @@
#include <poll.h>
#include <sys/ioctl.h>
#ifdef HAVE_LIBIDN2
#include <idn2.h>
#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");

View File

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

View File

@ -17,9 +17,11 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_LIBIDN
#include <idna.h>
#include <stringprep.h>
#if defined(HAVE_LIBIDN2)
# include <idn2.h>
#elif defined(HAVE_LIBIDN)
# include <idna.h>
# include <stringprep.h>
#endif
#include <endian.h>
@ -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) {

View File

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

View File

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