diff options
Diffstat (limited to 'src/shared/dns-domain.c')
-rw-r--r-- | src/shared/dns-domain.c | 202 |
1 files changed, 138 insertions, 64 deletions
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 4cf6355b71..429aa6d2cb 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -53,12 +53,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 +185,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 +202,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 +221,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 +231,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 +258,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 +278,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 +337,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 +364,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 +390,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 +442,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 +547,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 +593,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 +607,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 +653,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 +667,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 +864,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; } |