From 642900d3fa479c01d29ebe8268746d06d1c63703 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 20 Jul 2015 16:01:03 +0200 Subject: shared: dns-name - introduce dns_label_unescape_suffix() Intended to be called repeatedly, and returns then successive unescaped labels from the most to the least significant (left to right). This is slightly inefficient as it scans the string three times (two would be sufficient): once to find the end of the string, once to find the beginning of each label and lastly once to do the actual unescaping. The latter two could be done in one go, but that seemed unnecessarily convoluted. --- src/shared/dns-domain.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/dns-domain.h | 1 + src/test/test-dns-domain.c | 41 ++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) (limited to 'src') diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 20a44ce4e1..8a472fbcb4 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -114,6 +114,68 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { return r; } +/* @label_terminal: terminal character of a label, updated to point to the terminal character of + * the previous label (always skipping one dot) or to NULL if there are no more + * labels. */ +int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { + const char *terminal; + int r; + + assert(name); + assert(label_terminal); + assert(dest); + + /* no more labels */ + if (!*label_terminal) { + if (sz >= 1) + *dest = 0; + + return 0; + } + + assert(**label_terminal == '.' || **label_terminal == 0); + + /* skip current terminal character */ + terminal = *label_terminal - 1; + + /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + for (;;) { + if (terminal < name) { + /* reached the first label, so indicate that there are no more */ + terminal = NULL; + break; + } + + /* find the start of the last label */ + if (*terminal == '.') { + const char *y; + unsigned slashes = 0; + + for (y = terminal - 1; y >= name && *y == '\\'; y--) + slashes ++; + + if (slashes % 2 == 0) { + /* the '.' was not escaped */ + name = terminal + 1; + break; + } else { + terminal = y; + continue; + } + } + + terminal --; + } + + r = dns_label_unescape(&name, dest, sz); + if (r < 0) + return r; + + *label_terminal = terminal; + + return r; +} + int dns_label_escape(const char *p, size_t l, char **ret) { _cleanup_free_ char *s = NULL; char *q; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 00caf5d700..5728ce34bb 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -29,6 +29,7 @@ #define DNS_NAME_MAX 255 int dns_label_unescape(const char **name, 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 **ret); int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 527cdd3b54..9a5a2a1c23 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -50,6 +50,46 @@ static void test_dns_label_unescape(void) { test_dns_label_unescape_one("foobar.", "foobar", 20, 6); } +static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) { + char buffer[buffer_sz]; + const char *label; + int r; + + label = what + strlen(what); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret1); + if (r >= 0) + assert_se(streq(buffer, expect1)); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret2); + if (r >= 0) + assert_se(streq(buffer, expect2)); +} + +static void test_dns_label_unescape_suffix(void) { + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 6, 5, 0); + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 4, -ENOSPC, -ENOSPC); + test_dns_label_unescape_suffix_one("", "", "", 10, 0, 0); + test_dns_label_unescape_suffix_one("hallo\\.foobar", "hallo.foobar", "", 20, 12, 0); + test_dns_label_unescape_suffix_one("hallo.foobar", "foobar", "hallo", 10, 6, 5); + test_dns_label_unescape_suffix_one("hallo.foobar\n", "foobar", "foobar", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0); + test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL); + test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6); + test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3); + test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo...bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo\\.bar", "foo.bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo\\\\.bar", "bar", "foo\\", 20, 3, 4); + test_dns_label_unescape_suffix_one("foo\\\\\\.bar", "foo\\.bar", "", 20, 8, 0); +} + static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { _cleanup_free_ char *t = NULL; int r; @@ -180,6 +220,7 @@ static void test_dns_name_reverse(void) { int main(int argc, char *argv[]) { test_dns_label_unescape(); + test_dns_label_unescape_suffix(); test_dns_label_escape(); test_dns_name_normalize(); test_dns_name_equal(); -- cgit v1.2.3-54-g00ecf From 5dfd7011ba4821010825bf1259006bfb9918201c Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 20 Jul 2015 16:01:03 +0200 Subject: shared: dns-name - use the canonical dns name ordering The canonical DNS name ordering considers the rightmost label the most significant, we were considering it the least significant. This is important when implementing NSEC, which relies on the correct order. --- src/shared/dns-domain.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 8a472fbcb4..33925bfbd1 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -400,20 +400,23 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_ } int dns_name_compare_func(const void *a, const void *b) { - const char *x = a, *y = b; + const char *x, *y; int r, q, k, w; assert(a); assert(b); + x = (const char *) a + strlen(a); + y = (const char *) b + strlen(b); + for (;;) { char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - if (*x == 0 && *y == 0) + if (x == NULL && y == NULL) return 0; - r = dns_label_unescape(&x, la, sizeof(la)); - q = dns_label_unescape(&y, lb, sizeof(lb)); + r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); + q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); if (r < 0 || q < 0) return r - q; -- cgit v1.2.3-54-g00ecf From ae72b22c40569de7dba810073ea9da299491ef60 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 20 Jul 2015 02:02:45 +0200 Subject: shared: dns-name - add dns_name_between() Given three DNS names this function indicates if the second argument lies strictly between the first and the third according to the canonical DNS name order. Note that the order is circular, so the last name is considered to be before the first. --- src/shared/dns-domain.c | 22 ++++++++++++++++++++++ src/shared/dns-domain.h | 1 + src/test/test-dns-domain.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) (limited to 'src') diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 33925bfbd1..8a0dec1540 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -529,6 +529,28 @@ int dns_name_endswith(const char *name, const char *suffix) { } } +int dns_name_between(const char *a, const char *b, const char *c) { + int n; + + /* Determine if b is strictly greater than a and strictly smaller than c. + We consider the order of names to be circular, so that if a is + strictly greater than c, we consider b to be between them if it is + either greater than a or smaller than c. This is how the canonical + DNS name order used in NSEC records work. */ + + n = dns_name_compare_func(a, c); + if (n == 0) + return -EINVAL; + else if (n < 0) + /* a<---b--->c */ + return dns_name_compare_func(a, b) < 0 && + dns_name_compare_func(b, c) < 0; + else + /* <--b--c a--b--> */ + return dns_name_compare_func(b, c) < 0 || + dns_name_compare_func(a, b) < 0; +} + int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { const uint8_t *p; int r; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 5728ce34bb..bd50ad3e6d 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -50,6 +50,7 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_ int dns_name_compare_func(const void *a, const void *b); extern const struct hash_ops dns_name_hash_ops; +int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 9a5a2a1c23..31e110cf0d 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -160,6 +160,38 @@ static void test_dns_name_equal(void) { test_dns_name_equal_one("..", "..", -EINVAL); } +static void test_dns_name_between_one(const char *a, const char *b, const char *c, int ret) { + int r; + + r = dns_name_between(a, b, c); + assert_se(r == ret); + + r = dns_name_between(c, b, a); + if (ret >= 0) + assert_se(r == 0); + else + assert_se(r == ret); +} + +static void test_dns_name_between(void) { + /* see https://tools.ietf.org/html/rfc4034#section-6.1 + Note that we use "\033.z.example" in stead of "\001.z.example" as we + consider the latter invalid */ + test_dns_name_between_one("example", "a.example", "yljkjljk.a.example", true); + test_dns_name_between_one("a.example", "yljkjljk.a.example", "Z.a.example", true); + test_dns_name_between_one("yljkjljk.a.example", "Z.a.example", "zABC.a.EXAMPLE", true); + test_dns_name_between_one("Z.a.example", "zABC.a.EXAMPLE", "z.example", true); + test_dns_name_between_one("zABC.a.EXAMPLE", "z.example", "\\033.z.example", true); + test_dns_name_between_one("z.example", "\\033.z.example", "*.z.example", true); + test_dns_name_between_one("\\033.z.example", "*.z.example", "\\200.z.example", true); + test_dns_name_between_one("*.z.example", "\\200.z.example", "example", true); + test_dns_name_between_one("\\200.z.example", "example", "a.example", true); + + test_dns_name_between_one("example", "a.example", "example", -EINVAL); + test_dns_name_between_one("example", "example", "yljkjljk.a.example", false); + test_dns_name_between_one("example", "yljkjljk.a.example", "yljkjljk.a.example", false); +} + static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { assert_se(dns_name_endswith(a, b) == ret); } @@ -225,6 +257,7 @@ int main(int argc, char *argv[]) { test_dns_name_normalize(); test_dns_name_equal(); test_dns_name_endswith(); + test_dns_name_between(); test_dns_name_root(); test_dns_name_single_label(); test_dns_name_reverse(); -- cgit v1.2.3-54-g00ecf