summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2014-07-31 23:43:10 +0200
committerLennart Poettering <lennart@poettering.net>2014-08-01 00:58:12 +0200
commitbdf10b5b4d9e7abdc08bdca4b073d021b0043d1f (patch)
tree4ed35b1bc26e5457e471aa829b868878a81d6a49 /src/resolve
parentafbc4f267b45480e46fbcd59e90e399bc3dc7510 (diff)
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.
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/resolved-dns-domain.c132
-rw-r--r--src/resolve/resolved-dns-domain.h3
-rw-r--r--src/resolve/resolved-dns-packet.c12
3 files changed, 144 insertions, 3 deletions
diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c
index 1bb011d9e..80a2b3cd3 100644
--- a/src/resolve/resolved-dns-domain.c
+++ b/src/resolve/resolved-dns-domain.c
@@ -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;
diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h
index 16372fd94..e674c5d9c 100644
--- a/src/resolve/resolved-dns-domain.h
+++ b/src/resolve/resolved-dns-domain.h
@@ -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]);
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index ba6fb3d78..ba056f1f4 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -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;