summaryrefslogtreecommitdiff
path: root/src/shared/dns-domain.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/dns-domain.c')
-rw-r--r--src/shared/dns-domain.c211
1 files changed, 147 insertions, 64 deletions
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 4cf6355b71..0466857042 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -24,9 +24,18 @@
#include <stringprep.h>
#endif
+#include <endian.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+
#include "alloc-util.h"
#include "dns-domain.h"
+#include "hashmap.h"
#include "hexdecoct.h"
+#include "in-addr-util.h"
+#include "macro.h"
#include "parse-util.h"
#include "string-util.h"
#include "strv.h"
@@ -53,12 +62,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
if (*n == 0)
break;
- if (sz <= 0)
- return -ENOSPC;
-
if (r >= DNS_LABEL_MAX)
return -EINVAL;
+ if (sz <= 0)
+ return -ENOBUFS;
+
if (*n == '\\') {
/* Escaped character */
@@ -185,10 +194,14 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
char *q;
- if (l > DNS_LABEL_MAX)
+ /* DNS labels must be between 1 and 63 characters long. A
+ * zero-length label does not exist. See RFC 2182, Section
+ * 11. */
+
+ if (l <= 0 || l > DNS_LABEL_MAX)
return -EINVAL;
if (sz < 1)
- return -ENOSPC;
+ return -ENOBUFS;
assert(p);
assert(dest);
@@ -198,10 +211,11 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
if (*p == '.' || *p == '\\') {
+ /* Dot or backslash */
+
if (sz < 3)
- return -ENOSPC;
+ return -ENOBUFS;
- /* Dot or backslash */
*(q++) = '\\';
*(q++) = *p;
@@ -216,7 +230,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
/* Proper character */
if (sz < 2)
- return -ENOSPC;
+ return -ENOBUFS;
*(q++) = *p;
sz -= 1;
@@ -226,7 +240,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
/* Everything else */
if (sz < 5)
- return -ENOSPC;
+ return -ENOBUFS;
*(q++) = '\\';
*(q++) = '0' + (char) ((uint8_t) *p / 100);
@@ -253,7 +267,7 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
assert(p);
assert(ret);
- if (l > DNS_LABEL_MAX)
+ if (l <= 0 || l > DNS_LABEL_MAX)
return -EINVAL;
s = new(char, DNS_LABEL_ESCAPED_MAX);
@@ -273,32 +287,52 @@ int dns_label_escape_new(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) {
#ifdef HAVE_LIBIDN
_cleanup_free_ uint32_t *input = NULL;
- size_t input_size;
+ size_t input_size, l;
const char *p;
bool contains_8bit = false;
+ char buffer[DNS_LABEL_MAX+1];
assert(encoded);
assert(decoded);
- assert(decoded_max >= DNS_LABEL_MAX);
+
+ /* Converts an U-label into an A-label */
if (encoded_size <= 0)
- return 0;
+ return -EINVAL;
for (p = encoded; p < encoded + encoded_size; p++)
if ((uint8_t) *p > 127)
contains_8bit = true;
- if (!contains_8bit)
+ if (!contains_8bit) {
+ if (encoded_size > DNS_LABEL_MAX)
+ return -EINVAL;
+
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)
+ if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
return -EINVAL;
- return strlen(decoded);
+ l = strlen(buffer);
+
+ /* Verify that the the result is not longer than one DNS label. */
+ if (l <= 0 || l > DNS_LABEL_MAX)
+ return -EINVAL;
+ if (l > decoded_max)
+ return -ENOBUFS;
+
+ memcpy(decoded, buffer, l);
+
+ /* If there's room, append a trailing NUL byte, but only then */
+ if (decoded_max > l)
+ decoded[l] = 0;
+
+ return (int) l;
#else
return 0;
#endif
@@ -312,11 +346,14 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
uint32_t *output = NULL;
size_t w;
- /* To be invoked after unescaping */
+ /* To be invoked after unescaping. Converts an A-label into an U-label. */
assert(encoded);
assert(decoded);
+ if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
+ return -EINVAL;
+
if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
return 0;
@@ -336,11 +373,16 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
if (!result)
return -ENOMEM;
if (w <= 0)
- return 0;
- if (w+1 > decoded_max)
return -EINVAL;
+ if (w > decoded_max)
+ return -ENOBUFS;
+
+ memcpy(decoded, result, w);
+
+ /* Append trailing NUL byte if there's space, but only then. */
+ if (decoded_max > w)
+ decoded[w] = 0;
- memcpy(decoded, result, w+1);
return w;
#else
return 0;
@@ -357,7 +399,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
assert(a);
for (;;) {
- _cleanup_free_ char *t = NULL;
char label[DNS_LABEL_MAX];
int k;
@@ -410,6 +451,9 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
n += r;
}
+ if (n > DNS_HOSTNAME_MAX)
+ return -EINVAL;
+
if (_ret) {
if (!GREEDY_REALLOC(ret, allocated, n + 1))
return -ENOMEM;
@@ -512,24 +556,32 @@ int dns_name_equal(const char *x, const char *y) {
r = dns_label_unescape(&x, la, sizeof(la));
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;
+ if (r > 0) {
+ 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;
+ if (q > 0) {
+ w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
+ if (w < 0)
+ return w;
+ if (w > 0)
+ q = w;
+ }
+
+ /* If one name had fewer labels than the other, this
+ * will show up as empty label here, which the
+ * strcasecmp() below will properly consider different
+ * from a non-empty label. */
la[r] = lb[q] = 0;
- if (strcasecmp(la, lb))
+ if (strcasecmp(la, lb) != 0)
return false;
}
}
@@ -550,11 +602,13 @@ 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 (r > 0) {
+ 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;
@@ -562,11 +616,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
- w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
- if (w < 0)
- return w;
- if (w > 0)
- q = w;
+ if (q > 0) {
+ w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
+ if (w < 0)
+ return w;
+ if (w > 0)
+ q = w;
+ }
if (r == 0 && q == 0)
return true;
@@ -606,11 +662,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
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 (r > 0) {
+ k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
+ if (k < 0)
+ return k;
+ if (k > 0)
+ r = k;
+ }
if (!saved_after)
saved_after = n;
@@ -618,11 +676,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
- w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
- if (w < 0)
- return w;
- if (w > 0)
- q = w;
+ if (q > 0) {
+ w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
+ if (w < 0)
+ return w;
+ if (w > 0)
+ q = w;
+ }
if (r == 0 && q == 0)
break;
@@ -813,37 +873,60 @@ bool dns_name_is_single_label(const char *name) {
return dns_name_is_root(name);
}
-/* Encode a domain name according to RFC 1035 Section 3.1 */
-int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
- uint8_t *label_length;
- uint8_t *out;
+/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
+ uint8_t *label_length, *out;
int r;
- assert_return(buffer, -EINVAL);
- assert_return(domain, -EINVAL);
- assert_return(domain[0], -EINVAL);
+ assert(domain);
+ assert(buffer);
out = buffer;
do {
- /* reserve a byte for label length */
- if (len == 0)
+ /* Reserve a byte for label length */
+ if (len <= 0)
return -ENOBUFS;
len--;
label_length = out;
out++;
- /* convert and copy a single label */
+ /* Convert and copy a single label. Note that
+ * dns_label_unescape() returns 0 when it hits the end
+ * of the domain name, which we rely on here to encode
+ * the trailing NUL byte. */
r = dns_label_unescape(&domain, (char *) out, len);
if (r < 0)
return r;
- /* fill label length, move forward */
+ if (canonical) {
+ size_t i;
+
+ /* Optionally, output the name in DNSSEC
+ * canonical format, as described in RFC 4034,
+ * section 6.2. Or in other words: in
+ * lower-case. */
+
+ for (i = 0; i < (size_t) r; i++) {
+ if (out[i] >= 'A' && out[i] <= 'Z')
+ out[i] = out[i] - 'A' + 'a';
+ }
+ }
+
+ /* Fill label length, move forward */
*label_length = r;
out += r;
len -= r;
+
} while (r != 0);
+ /* Verify the maximum size of the encoded name. The trailing
+ * dot + NUL byte account are included this time, hence
+ * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
+ * time. */
+ if (out - buffer > DNS_HOSTNAME_MAX + 2)
+ return -EINVAL;
+
return out - buffer;
}