]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolve: reject host names with leading or trailing dashes in /etc/hosts
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 21 Nov 2018 21:58:13 +0000 (22:58 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 10 Dec 2018 08:56:56 +0000 (09:56 +0100)
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.

15 files changed:
src/libsystemd-network/ndisc-router.c
src/libsystemd-network/sd-dhcp-lease.c
src/network/networkd-network.c
src/resolve/resolved-bus.c
src/resolve/resolved-dns-dnssec.c
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-search-domain.c
src/resolve/resolved-dnssd.c
src/resolve/resolved-etc-hosts.c
src/resolve/resolved-manager.c
src/resolve/test-resolved-etc-hosts.c
src/shared/dns-domain.c
src/shared/dns-domain.h
src/test/test-dns-domain.c

index b982dcc07df82604f3716a2322db57016b037568..6935311b9a9f3f0302fbcef53f8a85fbfe2106a0 100644 (file)
@@ -676,7 +676,7 @@ _public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret)
                                 _cleanup_free_ char *normalized = NULL;
 
                                 e[n] = 0;
-                                r = dns_name_normalize(e, &normalized);
+                                r = dns_name_normalize(e, 0, &normalized);
                                 if (r < 0)
                                         return r;
 
index 8275d2f31a6f996be516b16eae8a90beec94bece..13badbf0bfb1ce642c583642e8287baa5b7fe6e4 100644 (file)
@@ -355,7 +355,7 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
                 return 0;
         }
 
-        r = dns_name_normalize(name, &normalized);
+        r = dns_name_normalize(name, 0, &normalized);
         if (r < 0)
                 return r;
 
index 0225f72bdbd911029512a58ded0c6be0c7db2804..34378887304e220674b14cc18e4dd0043cefc6eb 100644 (file)
@@ -652,7 +652,7 @@ int config_parse_domains(
                         domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */
 
                 } else {
-                        r = dns_name_normalize(domain, &normalized);
+                        r = dns_name_normalize(domain, 0, &normalized);
                         if (r < 0) {
                                 log_syntax(unit, LOG_ERR, filename, line, r, "'%s' is not a valid domain name, ignoring.", domain);
                                 continue;
index 75702d593f41a607c759f38b056400f13cb76f15..d80b16c9d989cc5fd9d8711c81fb3851c7b7a6e8 100644 (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
          * 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)
                 goto finish;
 
@@ -404,7 +404,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
                 if (r == 0)
                         continue;
 
-                r = dns_name_normalize(rr->ptr.name, &normalized);
+                r = dns_name_normalize(rr->ptr.name, 0, &normalized);
                 if (r < 0)
                         goto finish;
 
@@ -742,7 +742,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
         if (r < 0)
                 return r;
 
-        r = dns_name_normalize(rr->srv.name, &normalized);
+        r = dns_name_normalize(rr->srv.name, 0, &normalized);
         if (r < 0)
                 return r;
 
@@ -798,7 +798,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
         if (canonical) {
                 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)
                         return r;
         }
index 13da4e5991695cf9f896ae45f0e14bd5721ddb53..d9633629e85c4c712aa903f87ad1a15c4b1e2b27 100644 (file)
@@ -74,7 +74,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
                 return -ENOBUFS;
 
         for (;;) {
-                r = dns_label_unescape(&n, buffer, buffer_max);
+                r = dns_label_unescape(&n, buffer, buffer_max, 0);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -1705,7 +1705,7 @@ static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) {
                 return 0;
 
         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)
                 return r;
         if (r != 1 || label[0] != '*')
@@ -1827,13 +1827,13 @@ static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name)
                 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
                      * 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 {
                 r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix);
                 if (r < 0)
                         return r;
 
-                r = dns_name_concat("*", common_suffix, &wc);
+                r = dns_name_concat("*", common_suffix, 0, &wc);
         }
         if (r < 0)
                 return r;
index 0ff444acd066ae3a0e4394a5d0987e2f29a95bef..572271be95c37c8ebd1338eb4f52bd88bb4043a0 100644 (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)
                         goto fail;
 
index b67c734e04e62f22a69b65b9234ec970b06d5ea1..a1dffb08a3a28754e5819cac7eb73d020a6a09eb 100644 (file)
@@ -77,7 +77,7 @@ int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key
                 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)
                 return r;
 
@@ -222,7 +222,7 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr,
         if (search_domain) {
                 _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)
                         return r;
 
@@ -254,7 +254,7 @@ int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsRe
         if (search_domain) {
                 _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)
                         return r;
 
index 368ec4da19d5edae01dc13345a7ef5c885ae3b5e..21c2442c516b7698a765bf6aec6f67fcefc6826e 100644 (file)
@@ -19,7 +19,7 @@ int dns_search_domain_new(
         assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l);
         assert(name);
 
-        r = dns_name_normalize(name, &normalized);
+        r = dns_name_normalize(name, 0, &normalized);
         if (r < 0)
                 return r;
 
index ea96255dc1980b79845e963ee8fdc4bd731027b1..2c28ec227a5c601c053ccb7e3883e984255c9f4b 100644 (file)
@@ -228,10 +228,10 @@ int dnssd_update_rrs(DnssdService *s) {
         if (r < 0)
                 return r;
 
-        r = dns_name_concat(s->type, "local", &service_name);
+        r = dns_name_concat(s->type, "local", 0, &service_name);
         if (r < 0)
                 return r;
-        r = dns_name_concat(n, service_name, &full_name);
+        r = dns_name_concat(n, service_name, 0, &full_name);
         if (r < 0)
                 return r;
 
index 26a9e48c4ca9900378087caf610d48e8b5af1bd5..01cde4acf7b1e7ad0555507bc32b2f02eac08d62 100644 (file)
@@ -99,7 +99,7 @@ static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) {
 
                 found = true;
 
-                r = dns_name_is_valid(name);
+                r = dns_name_is_valid_ldh(name);
                 if (r <= 0) {
                         log_warning_errno(r, "/etc/hosts:%u: hostname \"%s\" is not valid, ignoring.", nr, name);
                         continue;
index 2174d660ff0e781f97d014ae1a0074b521907ffa..f6832917c11ae79a1c5f853fe6b9873adef67843 100644 (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");
 
         p = h;
-        r = dns_label_unescape(&p, label, sizeof label);
+        r = dns_label_unescape(&p, label, sizeof label, 0);
         if (r < 0)
                 return log_error_errno(r, "Failed to unescape host name: %m");
         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),
                                        "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)
                 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);
 
         p = fallback_hostname();
-        r = dns_label_unescape(&p, label, sizeof(label));
+        r = dns_label_unescape(&p, label, sizeof label, 0);
         if (r < 0)
                 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)
                 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)
                 return log_error_errno(r, "Failed to concatenate mDNS hostname: %m");
 
@@ -1147,7 +1147,7 @@ int manager_next_hostname(Manager *m) {
         if (r < 0)
                 return r;
 
-        r = dns_name_concat(h, "local", &k);
+        r = dns_name_concat(h, "local", 0, &k);
         if (r < 0)
                 return r;
 
index 0a08294d17effa559b47dfc9108672ac74f64ee6..27e67df01f01f3e82e06f7468bedef57adf6064f 100644 (file)
@@ -91,15 +91,9 @@ static void test_parse_etc_hosts(void) {
         assert_se(bn->n_allocated >= 1);
         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.
-         * Let's make this explicit by adding a test.
-         * See https://tools.ietf.org/html/rfc1035#section-2.3.1 */
-        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")));
-        }
+        /* See https://tools.ietf.org/html/rfc1035#section-2.3.1 */
+        FOREACH_STRING(s, "bad-dash-", "-bad-dash", "-bad-dash.bad-")
+                assert_se(!hashmap_get(hosts.by_name, s));
 
         assert_se(bn = hashmap_get(hosts.by_name, "before.comment"));
         assert_se(bn->n_addresses == 4);
index 5ff60fe1912d548c0af3b7de3ea026ed9d4382bb..5dd3734a508aece56a4978fbfedab662c7394c22 100644 (file)
 #include "strv.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;
-        char *d;
+        char *d, last_char = 0;
         int r = 0;
 
         assert(name);
@@ -36,13 +44,15 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
         d = dest;
 
         for (;;) {
-                if (*n == '.') {
-                        n++;
-                        break;
-                }
+                if (*n == 0 || *n == '.') {
+                        if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-')
+                                /* Trailing dash */
+                                return -EINVAL;
 
-                if (*n == 0)
+                        if (*n == '.')
+                                n++;
                         break;
+                }
 
                 if (r >= DNS_LABEL_MAX)
                         return -EINVAL;
@@ -52,6 +62,8 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
 
                 if (*n == '\\') {
                         /* Escaped character */
+                        if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES))
+                                return -EINVAL;
 
                         n++;
 
@@ -62,6 +74,10 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                         else if (IN_SET(*n, '\\', '.')) {
                                 /* Escaped backslash or dot */
 
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH))
+                                        return -EINVAL;
+
+                                last_char = *n;
                                 if (d)
                                         *(d++) = *n;
                                 sz--;
@@ -90,6 +106,11 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                                 if (k > 255)
                                         return -EINVAL;
 
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH) &&
+                                    !valid_ldh_char((char) k))
+                                        return -EINVAL;
+
+                                last_char = (char) k;
                                 if (d)
                                         *(d++) = (char) k;
                                 sz--;
@@ -103,6 +124,15 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
 
                         /* 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)
                                 *(d++) = *n;
                         sz--;
@@ -184,7 +214,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
                 terminal--;
         }
 
-        r = dns_label_unescape(&name, dest, sz);
+        r = dns_label_unescape(&name, dest, sz, 0);
         if (r < 0)
                 return r;
 
@@ -378,7 +408,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
 }
 #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;
         size_t n = 0, allocated = 0;
         const char *p;
@@ -395,7 +425,7 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
         for (;;) {
                 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)
                         return r;
                 if (r == 0) {
@@ -468,7 +498,7 @@ void dns_name_hash_func(const char *p, struct siphash *state) {
         for (;;) {
                 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)
                         break;
                 if (r == 0)
@@ -521,11 +551,11 @@ int dns_name_equal(const char *x, const char *y) {
         for (;;) {
                 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)
                         return r;
 
-                q = dns_label_unescape(&y, lb, sizeof(lb));
+                q = dns_label_unescape(&y, lb, sizeof lb, 0);
                 if (q < 0)
                         return q;
 
@@ -552,14 +582,14 @@ int dns_name_endswith(const char *name, const char *suffix) {
         for (;;) {
                 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)
                         return r;
 
                 if (!saved_n)
                         saved_n = n;
 
-                q = dns_label_unescape(&s, ls, sizeof(ls));
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
                 if (q < 0)
                         return q;
 
@@ -590,13 +620,13 @@ int dns_name_startswith(const char *name, const char *prefix) {
         for (;;) {
                 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)
                         return r;
                 if (r == 0)
                         return true;
 
-                q = dns_label_unescape(&n, ln, sizeof(ln));
+                q = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (q < 0)
                         return q;
 
@@ -625,14 +655,14 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 if (!saved_before)
                         saved_before = n;
 
-                r = dns_label_unescape(&n, ln, sizeof(ln));
+                r = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (r < 0)
                         return r;
 
                 if (!saved_after)
                         saved_after = n;
 
-                q = dns_label_unescape(&s, ls, sizeof(ls));
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
                 if (q < 0)
                         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 */
         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)
                 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++) {
                         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)
                                 return r;
                         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];
                         int x, y;
 
-                        r = dns_label_unescape(&p, label, sizeof(label));
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
                         if (r <= 0)
                                 return r;
                         if (r != 1)
@@ -779,7 +809,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                         if (x < 0)
                                 return -EINVAL;
 
-                        r = dns_label_unescape(&p, label, sizeof(label));
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
                         if (r <= 0)
                                 return r;
                         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
                  * of the domain name, which we rely on here to encode
                  * the trailing NUL byte. */
-                r = dns_label_unescape(&domain, (char *) out, len);
+                r = dns_label_unescape(&domain, (char *) out, len, 0);
                 if (r < 0)
                         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 */
 
-                r = dns_label_unescape(&name, label, sizeof(label));
+                r = dns_label_unescape(&name, label, sizeof label, 0);
                 if (r < 0)
                         return false;
                 if (r == 0)
@@ -974,7 +1004,7 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
                 return -EINVAL;
 
         if (!name)
-                return dns_name_concat(type, domain, ret);
+                return dns_name_concat(type, domain, 0, ret);
 
         if (!dns_service_name_is_valid(name))
                 return -EINVAL;
@@ -983,11 +1013,11 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
         if (r < 0)
                 return r;
 
-        r = dns_name_concat(type, domain, &n);
+        r = dns_name_concat(type, domain, 0, &n);
         if (r < 0)
                 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) {
@@ -1012,7 +1042,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
         assert(joined);
 
         /* 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)
                 return an;
 
@@ -1020,7 +1050,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
                 x++;
 
                 /* 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)
                         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 */
                         q = p;
-                        cn = dns_label_unescape(&p, c, sizeof(c));
+                        cn = dns_label_unescape(&p, c, sizeof(c), 0);
                         if (cn < 0)
                                 return cn;
 
@@ -1079,7 +1109,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
         d = joined;
 
 finish:
-        r = dns_name_normalize(d, &domain);
+        r = dns_name_normalize(d, 0, &domain);
         if (r < 0)
                 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];
-                r = dns_label_unescape(&x, la, sizeof(la));
+                r = dns_label_unescape(&x, la, sizeof la, 0);
                 if (r < 0)
                         return r;
 
                 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)
                         return q;
 
@@ -1297,13 +1327,13 @@ int dns_name_apply_idna(const char *name, char **ret) {
         for (;;) {
                 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)
                         return r;
                 if (r == 0)
                         break;
 
-                q = dns_label_apply_idna(label, r, label, sizeof(label));
+                q = dns_label_apply_idna(label, r, label, sizeof label);
                 if (q < 0)
                         return q;
                 if (q > 0)
index 42492ad7c0c1d2e7f4a25b447e1f51bd8d622496..6ed512c6b1656515c3a17b99fe994e6baedcf84e 100644 (file)
 /* Maximum number of labels per valid hostname */
 #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_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);
 
 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
@@ -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);
 #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 */
-        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) {
         int r;
 
         /* 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)
                 return 0;
         if (r < 0)
index 0d15d36c19cf6c89db4bf1785d4b9cc92ecd0c11..ead5311705d63c8ac6e8665ce71297f35de98942 100644 (file)
@@ -6,37 +6,57 @@
 #include "string-util.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];
         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);
+        if (r >= 0)
+                assert_se(streq(buffer, expect));
 
-        if (r < 0)
-                return;
+        w = what;
+        r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_LDH);
+        assert_se(r == ret_ldh);
+        if (r >= 0)
+                assert_se(streq(buffer, expect));
 
-        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));
 }
 
 static void test_dns_label_unescape(void) {
         log_info("/* %s */", __func__);
 
-        test_dns_label_unescape_one("hallo", "hallo", 6, 5);
-        test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS);
-        test_dns_label_unescape_one("", "", 10, 0);
-        test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12);
-        test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5);
-        test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL);
-        test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL);
-        test_dns_label_unescape_one("hallo\\032 ", "hallo  ", 20, 7);
-        test_dns_label_unescape_one(".", "", 20, 0);
-        test_dns_label_unescape_one("..", "", 20, -EINVAL);
-        test_dns_label_unescape_one(".foobar", "", 20, -EINVAL);
-        test_dns_label_unescape_one("foobar.", "foobar", 20, 6);
-        test_dns_label_unescape_one("foobar..", "foobar", 20, -EINVAL);
+        test_dns_label_unescape_one("hallo", "hallo", 6, 5, 5);
+        test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS, -ENOBUFS);
+        test_dns_label_unescape_one("", "", 10, 0, 0);
+        test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12, -EINVAL);
+        test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5, 5);
+        test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL, -EINVAL);
+        test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL, -EINVAL);
+        test_dns_label_unescape_one("hallo\\032 ", "hallo  ", 20, 7, -EINVAL);
+        test_dns_label_unescape_one(".", "", 20, 0, 0);
+        test_dns_label_unescape_one("..", "", 20, -EINVAL, -EINVAL);
+        test_dns_label_unescape_one(".foobar", "", 20, -EINVAL, -EINVAL);
+        test_dns_label_unescape_one("foobar.", "foobar", 20, 6, 6);
+        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) {
@@ -175,7 +195,7 @@ static void test_dns_name_normalize_one(const char *what, const char *expect, in
         _cleanup_free_ char *t = NULL;
         int r;
 
-        r = dns_name_normalize(what, &t);
+        r = dns_name_normalize(what, 0, &t);
         assert_se(r == ret);
 
         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) {
         _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));
 }
 
@@ -355,47 +375,63 @@ static void test_dns_name_concat(void) {
         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);
 
         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) {
         log_info("/* %s */", __func__);
 
-        test_dns_name_is_valid_one("foo", 1);
-        test_dns_name_is_valid_one("foo.", 1);
-        test_dns_name_is_valid_one("foo..", 0);
-        test_dns_name_is_valid_one("Foo", 1);
-        test_dns_name_is_valid_one("foo.bar", 1);
-        test_dns_name_is_valid_one("foo.bar.baz", 1);
-        test_dns_name_is_valid_one("", 1);
-        test_dns_name_is_valid_one("foo..bar", 0);
-        test_dns_name_is_valid_one(".foo.bar", 0);
-        test_dns_name_is_valid_one("foo.bar.", 1);
-        test_dns_name_is_valid_one("foo.bar..", 0);
-        test_dns_name_is_valid_one("\\zbar", 0);
-        test_dns_name_is_valid_one("ä", 1);
-        test_dns_name_is_valid_one("\n", 0);
+        test_dns_name_is_valid_one("foo",               1, 1);
+        test_dns_name_is_valid_one("foo.",              1, 1);
+        test_dns_name_is_valid_one("foo..",             0, 0);
+        test_dns_name_is_valid_one("Foo",               1, 1);
+        test_dns_name_is_valid_one("foo.bar",           1, 1);
+        test_dns_name_is_valid_one("foo.bar.baz",       1, 1);
+        test_dns_name_is_valid_one("",                  1, 1);
+        test_dns_name_is_valid_one("foo..bar",          0, 0);
+        test_dns_name_is_valid_one(".foo.bar",          0, 0);
+        test_dns_name_is_valid_one("foo.bar.",          1, 1);
+        test_dns_name_is_valid_one("foo.bar..",         0, 0);
+        test_dns_name_is_valid_one("\\zbar",            0, 0);
+        test_dns_name_is_valid_one("ä",                 1, 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 */
-        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 */
-        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 */
-        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 */
-        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 */
-        test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0);
+        test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0, 0);
 
         /* 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) {