resolved: handle IDNA domains

Make sure we format UTF-8 labels as IDNA when writing them to DNS
packets, and as native UTF-8 when writing them to mDNS or LLMNR packets.

When comparing or processing labels always consider native UTF-8 and
IDNA formats equivalent.
This commit is contained in:
Lennart Poettering 2014-07-31 23:43:10 +02:00
parent afbc4f267b
commit bdf10b5b4d
5 changed files with 166 additions and 6 deletions

View File

@ -4778,7 +4778,8 @@ systemd_resolved_LDADD = \
libsystemd-label.la \
libsystemd-internal.la \
libsystemd-shared.la \
-lm
-lm \
$(LIBIDN_LIBS)
rootlibexec_PROGRAMS += \
systemd-resolved
@ -4829,7 +4830,8 @@ test_dns_domain_LDADD = \
libsystemd-network.la \
libsystemd-label.la \
libsystemd-internal.la \
libsystemd-shared.la
libsystemd-shared.la \
$(LIBIDN_LIBS)
libnss_resolve_la_SOURCES = \
src/nss-resolve/nss-resolve.sym \
@ -4867,7 +4869,8 @@ systemd_resolve_host_SOURCES = \
systemd_resolve_host_LDADD = \
libsystemd-internal.la \
libsystemd-shared.la \
-lm
-lm \
$(LIBIDN_LIBS)
rootlibexec_PROGRAMS += \
systemd-resolve-host

View File

@ -810,6 +810,21 @@ if test "x$enable_libcurl" != "xno"; then
fi
AM_CONDITIONAL(HAVE_LIBCURL, [test "$have_libcurl" = "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])
fi
fi
AM_CONDITIONAL(HAVE_LIBIDN, [test "$have_libidn" = "yes"])
# ------------------------------------------------------------------------------
have_binfmt=no
AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool]))
@ -1326,6 +1341,7 @@ AC_MSG_RESULT([
CHKCONFIG: ${have_chkconfig}
GNUTLS: ${have_gnutls}
libcurl: ${have_libcurl}
libidn: ${have_libidn}
ELFUTILS: ${have_elfutils}
binfmt: ${have_binfmt}
vconsole: ${have_vconsole}

View File

@ -19,6 +19,11 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_LIBIDN
#include <idna.h>
#include <stringprep.h>
#endif
#include "resolved-dns-domain.h"
int dns_label_unescape(const char **name, char *dest, size_t sz) {
@ -164,6 +169,83 @@ int dns_label_escape(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
_cleanup_free_ uint32_t *input = NULL;
size_t input_size;
const char *p;
bool contains_8bit = false;
assert(encoded);
assert(decoded);
assert(decoded_max >= DNS_LABEL_MAX);
if (encoded_size <= 0)
return 0;
for (p = encoded; p < encoded + encoded_size; p++)
if ((uint8_t) *p > 127)
contains_8bit = true;
if (!contains_8bit)
return 0;
input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
if (!input)
return -ENOMEM;
if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
return -EINVAL;
return strlen(decoded);
#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;
uint32_t *output = NULL;
size_t w;
/* To be invoked after unescaping */
assert(encoded);
assert(decoded);
if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
return 0;
if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
return 0;
input = 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);
result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
if (!result)
return -ENOMEM;
if (w <= 0)
return 0;
if (w+1 > decoded_max)
return -EINVAL;
memcpy(decoded, result, w+1);
return w;
#else
return 0;
#endif
}
int dns_name_normalize(const char *s, char **_ret) {
_cleanup_free_ char *ret = NULL;
size_t n = 0, allocated = 0;
@ -176,6 +258,7 @@ int dns_name_normalize(const char *s, char **_ret) {
for (;;) {
_cleanup_free_ char *t = NULL;
char label[DNS_LABEL_MAX];
int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
@ -186,6 +269,12 @@ int dns_name_normalize(const char *s, char **_ret) {
break;
}
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0)
return k;
if (k > 0)
r = k;
r = dns_label_escape(label, r, &t);
if (r < 0)
return r;
@ -227,11 +316,18 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_
while (*p) {
char label[DNS_LABEL_MAX+1];
int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
break;
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0)
return k;
if (k > 0)
r = k;
label[r] = 0;
ascii_strlower(label);
@ -243,7 +339,7 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_
int dns_name_compare_func(const void *a, const void *b) {
const char *x = a, *y = b;
int r, q;
int r, q, k, w;
assert(a);
assert(b);
@ -259,6 +355,15 @@ int dns_name_compare_func(const void *a, const void *b) {
if (r < 0 || q < 0)
return r - q;
k = dns_label_undo_idna(la, r, la, sizeof(la));
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
if (k < 0 || w < 0)
return k - w;
if (k > 0)
r = k;
if (w > 0)
r = w;
la[r] = lb[q] = 0;
r = strcasecmp(la, lb);
if (r != 0)
@ -267,7 +372,7 @@ int dns_name_compare_func(const void *a, const void *b) {
}
int dns_name_equal(const char *x, const char *y) {
int r, q;
int r, q, k, w;
assert(x);
assert(y);
@ -282,9 +387,20 @@ int dns_name_equal(const char *x, const char *y) {
if (r < 0)
return r;
k = dns_label_undo_idna(la, r, la, sizeof(la));
if (k < 0)
return k;
if (k > 0)
r = k;
q = dns_label_unescape(&y, lb, sizeof(lb));
if (q < 0)
return q;
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
if (w < 0)
return w;
if (w > 0)
q = w;
la[r] = lb[q] = 0;
if (strcasecmp(la, lb))
@ -294,7 +410,7 @@ int dns_name_equal(const char *x, const char *y) {
int dns_name_endswith(const char *name, const char *suffix) {
const char *n, *s, *saved_n = NULL;
int r, q;
int r, q, k, w;
assert(name);
assert(suffix);
@ -308,6 +424,11 @@ int dns_name_endswith(const char *name, const char *suffix) {
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
if (!saved_n)
saved_n = n;
@ -315,6 +436,11 @@ int dns_name_endswith(const char *name, const char *suffix) {
q = dns_label_unescape(&s, ls, sizeof(ls));
if (r < 0)
return r;
w = dns_label_undo_idna(ls, r, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
if (r == 0 && q == 0)
return true;

View File

@ -30,6 +30,9 @@
int dns_label_unescape(const char **name, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char **ret);
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);
int dns_name_normalize(const char *s, char **_ret);
unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]);

View File

@ -390,6 +390,7 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) {
_cleanup_free_ char *s = NULL;
char label[DNS_LABEL_MAX];
size_t n;
int k;
n = PTR_TO_SIZE(hashmap_get(p->names, name));
if (n > 0) {
@ -414,6 +415,17 @@ int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) {
if (r < 0)
goto fail;
if (p->protocol == DNS_PROTOCOL_DNS)
k = dns_label_apply_idna(label, r, label, sizeof(label));
else
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0) {
r = k;
goto fail;
}
if (k > 0)
r = k;
r = dns_packet_append_label(p, label, r, &n);
if (r < 0)
goto fail;