resolve: reject host names with leading or trailing dashes in /etc/hosts

https://tools.ietf.org/html/rfc1035#section-2.3.1 says (approximately)
that only letters, numbers, and non-leading non-trailing dashes are allowed
(for entries with A/AAAA records). We set no restrictions.

hosts(5) says:
> Host names may contain only alphanumeric characters, minus signs ("-"), and
> periods (".").  They must begin with an alphabetic character and end with an
> alphanumeric character.

nss-files follows those rules, and will ignore names in /etc/hosts that do not
follow this rule.

Let's follow the documented rules for /etc/hosts. In particular, this makes us
consitent with nss-files, reducing surprises for the user.

I'm pretty sure we should apply stricter filtering to names received over DNS
and LLMNR and MDNS, but it's a bigger project, because the rules differ
depepending on which level the label appears (rules for top-level names are
stricter), and this patch takes the minimalistic approach and only changes
behaviour for /etc/hosts.

Escape syntax is also disallowed in /etc/hosts, even if the resulting character
would be allowed. Other tools that parse /etc/hosts do not support this, and
there is no need to use it because no allowed characters benefit from escaping.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-11-21 22:58:13 +01:00
parent bd00527779
commit 7470cc4c73
15 changed files with 193 additions and 117 deletions

View file

@ -676,7 +676,7 @@ _public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret)
_cleanup_free_ char *normalized = NULL; _cleanup_free_ char *normalized = NULL;
e[n] = 0; e[n] = 0;
r = dns_name_normalize(e, &normalized); r = dns_name_normalize(e, 0, &normalized);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -355,7 +355,7 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
return 0; return 0;
} }
r = dns_name_normalize(name, &normalized); r = dns_name_normalize(name, 0, &normalized);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -652,7 +652,7 @@ int config_parse_domains(
domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */ domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */
} else { } else {
r = dns_name_normalize(domain, &normalized); r = dns_name_normalize(domain, 0, &normalized);
if (r < 0) { if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "'%s' is not a valid domain name, ignoring.", domain); log_syntax(unit, LOG_ERR, filename, line, r, "'%s' is not a valid domain name, ignoring.", domain);
continue; continue;

View file

@ -191,7 +191,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
/* The key names are not necessarily normalized, make sure that they are when we return them to our bus /* The key names are not necessarily normalized, make sure that they are when we return them to our bus
* clients. */ * clients. */
r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized);
if (r < 0) if (r < 0)
goto finish; goto finish;
@ -404,7 +404,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
if (r == 0) if (r == 0)
continue; continue;
r = dns_name_normalize(rr->ptr.name, &normalized); r = dns_name_normalize(rr->ptr.name, 0, &normalized);
if (r < 0) if (r < 0)
goto finish; goto finish;
@ -742,7 +742,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_normalize(rr->srv.name, &normalized); r = dns_name_normalize(rr->srv.name, 0, &normalized);
if (r < 0) if (r < 0)
return r; return r;
@ -798,7 +798,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if (canonical) { if (canonical) {
normalized = mfree(normalized); normalized = mfree(normalized);
r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized);
if (r < 0) if (r < 0)
return r; return r;
} }

View file

@ -74,7 +74,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return -ENOBUFS; return -ENOBUFS;
for (;;) { for (;;) {
r = dns_label_unescape(&n, buffer, buffer_max); r = dns_label_unescape(&n, buffer, buffer_max, 0);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
@ -1705,7 +1705,7 @@ static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) {
return 0; return 0;
n = dns_resource_key_name(rr->key); n = dns_resource_key_name(rr->key);
r = dns_label_unescape(&n, label, sizeof(label)); r = dns_label_unescape(&n, label, sizeof label, 0);
if (r <= 0) if (r <= 0)
return r; return r;
if (r != 1 || label[0] != '*') if (r != 1 || label[0] != '*')
@ -1827,13 +1827,13 @@ static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name)
return r; return r;
if (r > 0) /* If the name we are interested in is a child of the NSEC RR, then append the asterisk to the NSEC if (r > 0) /* If the name we are interested in is a child of the NSEC RR, then append the asterisk to the NSEC
* RR's name. */ * RR's name. */
r = dns_name_concat("*", dns_resource_key_name(rr->key), &wc); r = dns_name_concat("*", dns_resource_key_name(rr->key), 0, &wc);
else { else {
r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix);
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_concat("*", common_suffix, &wc); r = dns_name_concat("*", common_suffix, 0, &wc);
} }
if (r < 0) if (r < 0)
return r; return r;

View file

@ -535,7 +535,7 @@ int dns_packet_append_name(
} }
} }
r = dns_label_unescape(&name, label, sizeof(label)); r = dns_label_unescape(&name, label, sizeof label, 0);
if (r < 0) if (r < 0)
goto fail; goto fail;

View file

@ -77,7 +77,7 @@ int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key
return 0; return 0;
} }
r = dns_name_concat(dns_resource_key_name(key), name, &joined); r = dns_name_concat(dns_resource_key_name(key), name, 0, &joined);
if (r < 0) if (r < 0)
return r; return r;
@ -222,7 +222,7 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr,
if (search_domain) { if (search_domain) {
_cleanup_free_ char *joined = NULL; _cleanup_free_ char *joined = NULL;
r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); r = dns_name_concat(dns_resource_key_name(key), search_domain, 0, &joined);
if (r < 0) if (r < 0)
return r; return r;
@ -254,7 +254,7 @@ int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsRe
if (search_domain) { if (search_domain) {
_cleanup_free_ char *joined = NULL; _cleanup_free_ char *joined = NULL;
r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); r = dns_name_concat(dns_resource_key_name(key), search_domain, 0, &joined);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -19,7 +19,7 @@ int dns_search_domain_new(
assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l);
assert(name); assert(name);
r = dns_name_normalize(name, &normalized); r = dns_name_normalize(name, 0, &normalized);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -228,10 +228,10 @@ int dnssd_update_rrs(DnssdService *s) {
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_concat(s->type, "local", &service_name); r = dns_name_concat(s->type, "local", 0, &service_name);
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_concat(n, service_name, &full_name); r = dns_name_concat(n, service_name, 0, &full_name);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -99,7 +99,7 @@ static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) {
found = true; found = true;
r = dns_name_is_valid(name); r = dns_name_is_valid_ldh(name);
if (r <= 0) { if (r <= 0) {
log_warning_errno(r, "/etc/hosts:%u: hostname \"%s\" is not valid, ignoring.", nr, name); log_warning_errno(r, "/etc/hosts:%u: hostname \"%s\" is not valid, ignoring.", nr, name);
continue; continue;

View file

@ -333,7 +333,7 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char
return log_debug_errno(r, "Can't determine system hostname: %m"); return log_debug_errno(r, "Can't determine system hostname: %m");
p = h; p = h;
r = dns_label_unescape(&p, label, sizeof label); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to unescape host name: %m"); return log_error_errno(r, "Failed to unescape host name: %m");
if (r == 0) if (r == 0)
@ -371,7 +371,7 @@ static int determine_hostname(char **full_hostname, char **llmnr_hostname, char
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"System hostname is 'localhost', ignoring."); "System hostname is 'localhost', ignoring.");
r = dns_name_concat(n, "local", mdns_hostname); r = dns_name_concat(n, "local", 0, mdns_hostname);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to determine mDNS hostname: %m"); return log_error_errno(r, "Failed to determine mDNS hostname: %m");
@ -403,7 +403,7 @@ static int make_fallback_hostnames(char **full_hostname, char **llmnr_hostname,
assert(mdns_hostname); assert(mdns_hostname);
p = fallback_hostname(); p = fallback_hostname();
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to unescape fallback host name: %m"); return log_error_errno(r, "Failed to unescape fallback host name: %m");
@ -413,7 +413,7 @@ static int make_fallback_hostnames(char **full_hostname, char **llmnr_hostname,
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to escape fallback hostname: %m"); return log_error_errno(r, "Failed to escape fallback hostname: %m");
r = dns_name_concat(n, "local", &m); r = dns_name_concat(n, "local", 0, &m);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to concatenate mDNS hostname: %m"); return log_error_errno(r, "Failed to concatenate mDNS hostname: %m");
@ -1147,7 +1147,7 @@ int manager_next_hostname(Manager *m) {
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_concat(h, "local", &k); r = dns_name_concat(h, "local", 0, &k);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -91,15 +91,9 @@ static void test_parse_etc_hosts(void) {
assert_se(bn->n_allocated >= 1); assert_se(bn->n_allocated >= 1);
assert_se(address_equal_4(bn->addresses[0], inet_addr("1.2.3.6"))); assert_se(address_equal_4(bn->addresses[0], inet_addr("1.2.3.6")));
/* Those names do not follow the LDH rule, but so far we allow them. /* See https://tools.ietf.org/html/rfc1035#section-2.3.1 */
* Let's make this explicit by adding a test. FOREACH_STRING(s, "bad-dash-", "-bad-dash", "-bad-dash.bad-")
* See https://tools.ietf.org/html/rfc1035#section-2.3.1 */ assert_se(!hashmap_get(hosts.by_name, s));
FOREACH_STRING(s, "bad-dash-", "-bad-dash", "-bad-dash.bad-") {
assert_se(bn = hashmap_get(hosts.by_name, s));
assert_se(bn->n_addresses == 1);
assert_se(bn->n_allocated >= 1);
assert_se(address_equal_4(bn->addresses[0], inet_addr("1.2.3.7")));
}
assert_se(bn = hashmap_get(hosts.by_name, "before.comment")); assert_se(bn = hashmap_get(hosts.by_name, "before.comment"));
assert_se(bn->n_addresses == 4); assert_se(bn->n_addresses == 4);

View file

@ -24,9 +24,17 @@
#include "strv.h" #include "strv.h"
#include "utf8.h" #include "utf8.h"
int dns_label_unescape(const char **name, char *dest, size_t sz) { static bool valid_ldh_char(char c) {
return
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '-';
}
int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) {
const char *n; const char *n;
char *d; char *d, last_char = 0;
int r = 0; int r = 0;
assert(name); assert(name);
@ -36,14 +44,16 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
d = dest; d = dest;
for (;;) { for (;;) {
if (*n == '.') { if (*n == 0 || *n == '.') {
if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-')
/* Trailing dash */
return -EINVAL;
if (*n == '.')
n++; n++;
break; break;
} }
if (*n == 0)
break;
if (r >= DNS_LABEL_MAX) if (r >= DNS_LABEL_MAX)
return -EINVAL; return -EINVAL;
@ -52,6 +62,8 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
if (*n == '\\') { if (*n == '\\') {
/* Escaped character */ /* Escaped character */
if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES))
return -EINVAL;
n++; n++;
@ -62,6 +74,10 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
else if (IN_SET(*n, '\\', '.')) { else if (IN_SET(*n, '\\', '.')) {
/* Escaped backslash or dot */ /* Escaped backslash or dot */
if (FLAGS_SET(flags, DNS_LABEL_LDH))
return -EINVAL;
last_char = *n;
if (d) if (d)
*(d++) = *n; *(d++) = *n;
sz--; sz--;
@ -90,6 +106,11 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
if (k > 255) if (k > 255)
return -EINVAL; return -EINVAL;
if (FLAGS_SET(flags, DNS_LABEL_LDH) &&
!valid_ldh_char((char) k))
return -EINVAL;
last_char = (char) k;
if (d) if (d)
*(d++) = (char) k; *(d++) = (char) k;
sz--; sz--;
@ -103,6 +124,15 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
/* Normal character */ /* Normal character */
if (FLAGS_SET(flags, DNS_LABEL_LDH)) {
if (!valid_ldh_char(*n))
return -EINVAL;
if (r == 0 && *n == '-')
/* Leading dash */
return -EINVAL;
}
last_char = *n;
if (d) if (d)
*(d++) = *n; *(d++) = *n;
sz--; sz--;
@ -184,7 +214,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
terminal--; terminal--;
} }
r = dns_label_unescape(&name, dest, sz); r = dns_label_unescape(&name, dest, sz, 0);
if (r < 0) if (r < 0)
return r; return r;
@ -378,7 +408,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
} }
#endif #endif
int dns_name_concat(const char *a, const char *b, char **_ret) { int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) {
_cleanup_free_ char *ret = NULL; _cleanup_free_ char *ret = NULL;
size_t n = 0, allocated = 0; size_t n = 0, allocated = 0;
const char *p; const char *p;
@ -395,7 +425,7 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
for (;;) { for (;;) {
char label[DNS_LABEL_MAX]; char label[DNS_LABEL_MAX];
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, flags);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) { if (r == 0) {
@ -468,7 +498,7 @@ void dns_name_hash_func(const char *p, struct siphash *state) {
for (;;) { for (;;) {
char label[DNS_LABEL_MAX+1]; char label[DNS_LABEL_MAX+1];
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r < 0) if (r < 0)
break; break;
if (r == 0) if (r == 0)
@ -521,11 +551,11 @@ int dns_name_equal(const char *x, const char *y) {
for (;;) { for (;;) {
char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
r = dns_label_unescape(&x, la, sizeof(la)); r = dns_label_unescape(&x, la, sizeof la, 0);
if (r < 0) if (r < 0)
return r; return r;
q = dns_label_unescape(&y, lb, sizeof(lb)); q = dns_label_unescape(&y, lb, sizeof lb, 0);
if (q < 0) if (q < 0)
return q; return q;
@ -552,14 +582,14 @@ int dns_name_endswith(const char *name, const char *suffix) {
for (;;) { for (;;) {
char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
r = dns_label_unescape(&n, ln, sizeof(ln)); r = dns_label_unescape(&n, ln, sizeof ln, 0);
if (r < 0) if (r < 0)
return r; return r;
if (!saved_n) if (!saved_n)
saved_n = n; saved_n = n;
q = dns_label_unescape(&s, ls, sizeof(ls)); q = dns_label_unescape(&s, ls, sizeof ls, 0);
if (q < 0) if (q < 0)
return q; return q;
@ -590,13 +620,13 @@ int dns_name_startswith(const char *name, const char *prefix) {
for (;;) { for (;;) {
char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
r = dns_label_unescape(&p, lp, sizeof(lp)); r = dns_label_unescape(&p, lp, sizeof lp, 0);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
return true; return true;
q = dns_label_unescape(&n, ln, sizeof(ln)); q = dns_label_unescape(&n, ln, sizeof ln, 0);
if (q < 0) if (q < 0)
return q; return q;
@ -625,14 +655,14 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
if (!saved_before) if (!saved_before)
saved_before = n; saved_before = n;
r = dns_label_unescape(&n, ln, sizeof(ln)); r = dns_label_unescape(&n, ln, sizeof ln, 0);
if (r < 0) if (r < 0)
return r; return r;
if (!saved_after) if (!saved_after)
saved_after = n; saved_after = n;
q = dns_label_unescape(&s, ls, sizeof(ls)); q = dns_label_unescape(&s, ls, sizeof ls, 0);
if (q < 0) if (q < 0)
return q; return q;
@ -655,7 +685,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
/* Found it! Now generate the new name */ /* Found it! Now generate the new name */
prefix = strndupa(name, saved_before - name); prefix = strndupa(name, saved_before - name);
r = dns_name_concat(prefix, new_suffix, ret); r = dns_name_concat(prefix, new_suffix, 0, ret);
if (r < 0) if (r < 0)
return r; return r;
@ -733,7 +763,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
for (i = 0; i < ELEMENTSOF(a); i++) { for (i = 0; i < ELEMENTSOF(a); i++) {
char label[DNS_LABEL_MAX+1]; char label[DNS_LABEL_MAX+1];
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
@ -770,7 +800,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
char label[DNS_LABEL_MAX+1]; char label[DNS_LABEL_MAX+1];
int x, y; int x, y;
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r <= 0) if (r <= 0)
return r; return r;
if (r != 1) if (r != 1)
@ -779,7 +809,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
if (x < 0) if (x < 0)
return -EINVAL; return -EINVAL;
r = dns_label_unescape(&p, label, sizeof(label)); r = dns_label_unescape(&p, label, sizeof label, 0);
if (r <= 0) if (r <= 0)
return r; return r;
if (r != 1) if (r != 1)
@ -847,7 +877,7 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
* dns_label_unescape() returns 0 when it hits the end * dns_label_unescape() returns 0 when it hits the end
* of the domain name, which we rely on here to encode * of the domain name, which we rely on here to encode
* the trailing NUL byte. */ * the trailing NUL byte. */
r = dns_label_unescape(&domain, (char *) out, len); r = dns_label_unescape(&domain, (char *) out, len, 0);
if (r < 0) if (r < 0)
return r; return r;
@ -914,7 +944,7 @@ bool dns_srv_type_is_valid(const char *name) {
/* This more or less implements RFC 6335, Section 5.1 */ /* This more or less implements RFC 6335, Section 5.1 */
r = dns_label_unescape(&name, label, sizeof(label)); r = dns_label_unescape(&name, label, sizeof label, 0);
if (r < 0) if (r < 0)
return false; return false;
if (r == 0) if (r == 0)
@ -974,7 +1004,7 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
return -EINVAL; return -EINVAL;
if (!name) if (!name)
return dns_name_concat(type, domain, ret); return dns_name_concat(type, domain, 0, ret);
if (!dns_service_name_is_valid(name)) if (!dns_service_name_is_valid(name))
return -EINVAL; return -EINVAL;
@ -983,11 +1013,11 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
if (r < 0) if (r < 0)
return r; return r;
r = dns_name_concat(type, domain, &n); r = dns_name_concat(type, domain, 0, &n);
if (r < 0) if (r < 0)
return r; return r;
return dns_name_concat(escaped, n, ret); return dns_name_concat(escaped, n, 0, ret);
} }
static bool dns_service_name_label_is_valid(const char *label, size_t n) { static bool dns_service_name_label_is_valid(const char *label, size_t n) {
@ -1012,7 +1042,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
assert(joined); assert(joined);
/* Get first label from the full name */ /* Get first label from the full name */
an = dns_label_unescape(&p, a, sizeof(a)); an = dns_label_unescape(&p, a, sizeof(a), 0);
if (an < 0) if (an < 0)
return an; return an;
@ -1020,7 +1050,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
x++; x++;
/* If there was a first label, try to get the second one */ /* If there was a first label, try to get the second one */
bn = dns_label_unescape(&p, b, sizeof(b)); bn = dns_label_unescape(&p, b, sizeof(b), 0);
if (bn < 0) if (bn < 0)
return bn; return bn;
@ -1029,7 +1059,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
/* If there was a second label, try to get the third one */ /* If there was a second label, try to get the third one */
q = p; q = p;
cn = dns_label_unescape(&p, c, sizeof(c)); cn = dns_label_unescape(&p, c, sizeof(c), 0);
if (cn < 0) if (cn < 0)
return cn; return cn;
@ -1079,7 +1109,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
d = joined; d = joined;
finish: finish:
r = dns_name_normalize(d, &domain); r = dns_name_normalize(d, 0, &domain);
if (r < 0) if (r < 0)
return r; return r;
@ -1224,12 +1254,12 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
} }
x = a_labels[n - 1 - k]; x = a_labels[n - 1 - k];
r = dns_label_unescape(&x, la, sizeof(la)); r = dns_label_unescape(&x, la, sizeof la, 0);
if (r < 0) if (r < 0)
return r; return r;
y = b_labels[m - 1 - k]; y = b_labels[m - 1 - k];
q = dns_label_unescape(&y, lb, sizeof(lb)); q = dns_label_unescape(&y, lb, sizeof lb, 0);
if (q < 0) if (q < 0)
return q; return q;
@ -1297,13 +1327,13 @@ int dns_name_apply_idna(const char *name, char **ret) {
for (;;) { for (;;) {
char label[DNS_LABEL_MAX]; char label[DNS_LABEL_MAX];
r = dns_label_unescape(&name, label, sizeof(label)); r = dns_label_unescape(&name, label, sizeof label, 0);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
break; break;
q = dns_label_apply_idna(label, r, label, sizeof(label)); q = dns_label_apply_idna(label, r, label, sizeof label);
if (q < 0) if (q < 0)
return q; return q;
if (q > 0) if (q > 0)

View file

@ -24,13 +24,18 @@
/* Maximum number of labels per valid hostname */ /* Maximum number of labels per valid hostname */
#define DNS_N_LABELS_MAX 127 #define DNS_N_LABELS_MAX 127
int dns_label_unescape(const char **name, char *dest, size_t sz); typedef enum DNSLabelFlags {
DNS_LABEL_LDH = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */
DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */
} DNSLabelFlags;
int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags);
int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
int dns_label_escape_new(const char *p, size_t l, char **ret); int dns_label_escape_new(const char *p, size_t l, char **ret);
static inline int dns_name_parent(const char **name) { static inline int dns_name_parent(const char **name) {
return dns_label_unescape(name, NULL, DNS_LABEL_MAX); return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0);
} }
#if HAVE_LIBIDN #if HAVE_LIBIDN
@ -38,18 +43,29 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
int dns_label_undo_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 #endif
int dns_name_concat(const char *a, const char *b, char **ret); int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret);
static inline int dns_name_normalize(const char *s, char **ret) { static inline int dns_name_normalize(const char *s, DNSLabelFlags flags, char **ret) {
/* dns_name_concat() normalizes as a side-effect */ /* dns_name_concat() normalizes as a side-effect */
return dns_name_concat(s, NULL, ret); return dns_name_concat(s, NULL, flags, ret);
} }
static inline int dns_name_is_valid(const char *s) { static inline int dns_name_is_valid(const char *s) {
int r; int r;
/* dns_name_normalize() verifies as a side effect */ /* dns_name_normalize() verifies as a side effect */
r = dns_name_normalize(s, NULL); r = dns_name_normalize(s, 0, NULL);
if (r == -EINVAL)
return 0;
if (r < 0)
return r;
return 1;
}
static inline int dns_name_is_valid_ldh(const char *s) {
int r;
r = dns_name_concat(s, NULL, DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES, NULL);
if (r == -EINVAL) if (r == -EINVAL)
return 0; return 0;
if (r < 0) if (r < 0)

View file

@ -6,37 +6,57 @@
#include "string-util.h" #include "string-util.h"
#include "tests.h" #include "tests.h"
static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret) { static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret, int ret_ldh) {
char buffer[buffer_sz]; char buffer[buffer_sz];
int r; int r;
const char *w = what;
log_info("%s, %s, %zu, →%d", what, expect, buffer_sz, ret); log_info("%s, %s, %zu, →%d/%d", what, expect, buffer_sz, ret, ret_ldh);
r = dns_label_unescape(&what, buffer, buffer_sz); r = dns_label_unescape(&w, buffer, buffer_sz, 0);
assert_se(r == ret); assert_se(r == ret);
if (r >= 0)
assert_se(streq(buffer, expect));
if (r < 0) w = what;
return; r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_LDH);
assert_se(r == ret_ldh);
if (r >= 0)
assert_se(streq(buffer, expect));
w = what;
r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_NO_ESCAPES);
const int ret_noe = strchr(what, '\\') ? -EINVAL : ret;
assert_se(r == ret_noe);
if (r >= 0)
assert_se(streq(buffer, expect)); assert_se(streq(buffer, expect));
} }
static void test_dns_label_unescape(void) { static void test_dns_label_unescape(void) {
log_info("/* %s */", __func__); log_info("/* %s */", __func__);
test_dns_label_unescape_one("hallo", "hallo", 6, 5); test_dns_label_unescape_one("hallo", "hallo", 6, 5, 5);
test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS); test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS, -ENOBUFS);
test_dns_label_unescape_one("", "", 10, 0); test_dns_label_unescape_one("", "", 10, 0, 0);
test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12); test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12, -EINVAL);
test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5); test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5, 5);
test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL); test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL); test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7); test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7, -EINVAL);
test_dns_label_unescape_one(".", "", 20, 0); test_dns_label_unescape_one(".", "", 20, 0, 0);
test_dns_label_unescape_one("..", "", 20, -EINVAL); test_dns_label_unescape_one("..", "", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); test_dns_label_unescape_one(".foobar", "", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_one("foobar.", "foobar", 20, 6); test_dns_label_unescape_one("foobar.", "foobar", 20, 6, 6);
test_dns_label_unescape_one("foobar..", "foobar", 20, -EINVAL); test_dns_label_unescape_one("foobar..", "foobar", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_one("foo-bar", "foo-bar", 20, 7, 7);
test_dns_label_unescape_one("foo-", "foo-", 20, 4, -EINVAL);
test_dns_label_unescape_one("-foo", "-foo", 20, 4, -EINVAL);
test_dns_label_unescape_one("-foo-", "-foo-", 20, 5, -EINVAL);
test_dns_label_unescape_one("foo-.", "foo-", 20, 4, -EINVAL);
test_dns_label_unescape_one("foo.-", "foo", 20, 3, 3);
test_dns_label_unescape_one("foo\\032", "foo ", 20, 4, -EINVAL);
test_dns_label_unescape_one("foo\\045", "foo-", 20, 4, -EINVAL);
test_dns_label_unescape_one("głąb", "głąb", 20, 6, -EINVAL);
} }
static void test_dns_name_to_wire_format_one(const char *what, const char *expect, size_t buffer_sz, int ret) { static void test_dns_name_to_wire_format_one(const char *what, const char *expect, size_t buffer_sz, int ret) {
@ -175,7 +195,7 @@ static void test_dns_name_normalize_one(const char *what, const char *expect, in
_cleanup_free_ char *t = NULL; _cleanup_free_ char *t = NULL;
int r; int r;
r = dns_name_normalize(what, &t); r = dns_name_normalize(what, 0, &t);
assert_se(r == ret); assert_se(r == ret);
if (r < 0) if (r < 0)
@ -336,7 +356,7 @@ static void test_dns_name_reverse(void) {
static void test_dns_name_concat_one(const char *a, const char *b, int r, const char *result) { static void test_dns_name_concat_one(const char *a, const char *b, int r, const char *result) {
_cleanup_free_ char *p = NULL; _cleanup_free_ char *p = NULL;
assert_se(dns_name_concat(a, b, &p) == r); assert_se(dns_name_concat(a, b, 0, &p) == r);
assert_se(streq_ptr(p, result)); assert_se(streq_ptr(p, result));
} }
@ -355,47 +375,63 @@ static void test_dns_name_concat(void) {
test_dns_name_concat_one(NULL, "foo", 0, "foo"); test_dns_name_concat_one(NULL, "foo", 0, "foo");
} }
static void test_dns_name_is_valid_one(const char *s, int ret) { static void test_dns_name_is_valid_one(const char *s, int ret, int ret_ldh) {
log_info("%s, →%d", s, ret); log_info("%s, →%d", s, ret);
assert_se(dns_name_is_valid(s) == ret); assert_se(dns_name_is_valid(s) == ret);
assert_se(dns_name_is_valid_ldh(s) == ret_ldh);
} }
static void test_dns_name_is_valid(void) { static void test_dns_name_is_valid(void) {
log_info("/* %s */", __func__); log_info("/* %s */", __func__);
test_dns_name_is_valid_one("foo", 1); test_dns_name_is_valid_one("foo", 1, 1);
test_dns_name_is_valid_one("foo.", 1); test_dns_name_is_valid_one("foo.", 1, 1);
test_dns_name_is_valid_one("foo..", 0); test_dns_name_is_valid_one("foo..", 0, 0);
test_dns_name_is_valid_one("Foo", 1); test_dns_name_is_valid_one("Foo", 1, 1);
test_dns_name_is_valid_one("foo.bar", 1); test_dns_name_is_valid_one("foo.bar", 1, 1);
test_dns_name_is_valid_one("foo.bar.baz", 1); test_dns_name_is_valid_one("foo.bar.baz", 1, 1);
test_dns_name_is_valid_one("", 1); test_dns_name_is_valid_one("", 1, 1);
test_dns_name_is_valid_one("foo..bar", 0); test_dns_name_is_valid_one("foo..bar", 0, 0);
test_dns_name_is_valid_one(".foo.bar", 0); test_dns_name_is_valid_one(".foo.bar", 0, 0);
test_dns_name_is_valid_one("foo.bar.", 1); test_dns_name_is_valid_one("foo.bar.", 1, 1);
test_dns_name_is_valid_one("foo.bar..", 0); test_dns_name_is_valid_one("foo.bar..", 0, 0);
test_dns_name_is_valid_one("\\zbar", 0); test_dns_name_is_valid_one("\\zbar", 0, 0);
test_dns_name_is_valid_one("ä", 1); test_dns_name_is_valid_one("ä", 1, 0);
test_dns_name_is_valid_one("\n", 0); test_dns_name_is_valid_one("\n", 0, 0);
test_dns_name_is_valid_one("dash-", 1, 0);
test_dns_name_is_valid_one("-dash", 1, 0);
test_dns_name_is_valid_one("dash-dash", 1, 1);
test_dns_name_is_valid_one("foo.dash-", 1, 0);
test_dns_name_is_valid_one("foo.-dash", 1, 0);
test_dns_name_is_valid_one("foo.dash-dash", 1, 1);
test_dns_name_is_valid_one("foo.dash-.bar", 1, 0);
test_dns_name_is_valid_one("foo.-dash.bar", 1, 0);
test_dns_name_is_valid_one("foo.dash-dash.bar", 1, 1);
test_dns_name_is_valid_one("dash-.bar", 1, 0);
test_dns_name_is_valid_one("-dash.bar", 1, 0);
test_dns_name_is_valid_one("dash-dash.bar", 1, 1);
test_dns_name_is_valid_one("-.bar", 1, 0);
test_dns_name_is_valid_one("foo.-", 1, 0);
/* 256 characters */ /* 256 characters */
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345", 0); test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345", 0, 0);
/* 255 characters */ /* 255 characters */
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a1234", 0); test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a1234", 0, 0);
/* 254 characters */ /* 254 characters */
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", 0); test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", 0, 0);
/* 253 characters */ /* 253 characters */
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", 1); test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", 1, 1);
/* label of 64 chars length */ /* label of 64 chars length */
test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0); test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0, 0);
/* label of 63 chars length */ /* label of 63 chars length */
test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a12", 1); test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a12", 1, 1);
} }
static void test_dns_service_name_is_valid(void) { static void test_dns_service_name_is_valid(void) {