From 0e8eedbb490b8928d4fa4e33f565a3cda5013018 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 23 Nov 2015 21:21:13 +0100 Subject: dns-domain: add calls to join/split SRV/DNS-SD service domains This adds dns_service_join() and dns_service_split() which may be used to concatenate a DNS-SD service name, am SRV service type string, and a domain name into a full resolvable DNS domain name string. If the service name is specified as NULL, only the type and domain are appended, to implement classic, non-DNS-SD SRV lookups. The reverse is dns_service_split() which takes the full name, and split it into the three components again. --- src/shared/dns-domain.c | 193 +++++++++++++++++++++++++++++++++++++++++---- src/shared/dns-domain.h | 3 + src/test/test-dns-domain.c | 62 ++++++++++++++- 3 files changed, 240 insertions(+), 18 deletions(-) diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 014e0bd70d..e08143c3be 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -751,6 +751,34 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) { return out - buffer; } +static bool srv_type_label_is_valid(const char *label, size_t n) { + size_t k; + + assert(label); + + if (n < 2) /* Label needs to be at least 2 chars long */ + return false; + + if (label[0] != '_') /* First label char needs to be underscore */ + return false; + + /* Second char must be a letter */ + if (!(label[1] >= 'A' && label[1] <= 'Z') && + !(label[1] >= 'a' && label[1] <= 'z')) + return false; + + /* Third and further chars must be alphanumeric or a hyphen */ + for (k = 2; k < n; k++) { + if (!(label[k] >= 'A' && label[k] <= 'Z') && + !(label[k] >= 'a' && label[k] <= 'z') && + !(label[k] >= '0' && label[k] <= '9') && + label[k] != '-') + return false; + } + + return true; +} + int dns_srv_type_verify(const char *name) { unsigned c = 0; int r; @@ -760,7 +788,6 @@ int dns_srv_type_verify(const char *name) { for (;;) { char label[DNS_LABEL_MAX]; - int k; /* This more or less implements RFC 6335, Section 5.1 */ @@ -770,28 +797,18 @@ int dns_srv_type_verify(const char *name) { if (r < 0) return r; if (r == 0) - return c >= 2; /* At least two labels */ - if (r < 2) /* Label needs to be at least 2 chars long */ - return 0; - if (label[0] != '_') /* First label char needs to be underscore */ - return 0; + break; - /* Second char must be a letter */ - if (!(label[1] >= 'A' && label[1] <= 'Z') && - !(label[1] >= 'a' && label[1] <= 'z')) + if (c >= 2) return 0; - /* Third and further chars must be alphanumeric or a hyphen */ - for (k = 2; k < r; k++) { - if (!(label[k] >= 'A' && label[k] <= 'Z') && - !(label[k] >= 'a' && label[k] <= 'z') && - !(label[k] >= '0' && label[k] <= '9') && - label[k] != '-') - return 0; - } + if (!srv_type_label_is_valid(label, r)) + return 0; c++; } + + return c == 2; /* exactly two labels */ } bool dns_service_name_is_valid(const char *name) { @@ -816,3 +833,145 @@ bool dns_service_name_is_valid(const char *name) { return true; } + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { + _cleanup_free_ char *escaped = NULL, *n = NULL; + int r; + + assert(type); + assert(domain); + assert(ret); + + if (!dns_srv_type_verify(type)) + return -EINVAL; + + if (!name) + return dns_name_concat(type, domain, ret); + + if (!dns_service_name_is_valid(name)) + return -EINVAL; + + r = dns_label_escape(name, strlen(name), &escaped); + if (r < 0) + return r; + + r = dns_name_concat(type, domain, &n); + if (r < 0) + return r; + + return dns_name_concat(escaped, n, ret); +} + +static bool dns_service_name_label_is_valid(const char *label, size_t n) { + char *s; + + assert(label); + + if (memchr(label, 0, n)) + return false; + + s = strndupa(label, n); + return dns_service_name_is_valid(s); +} + +int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + const char *p = joined, *q = NULL, *d = NULL; + char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; + int an, bn, cn, r; + unsigned x = 0; + + assert(joined); + + /* Get first label from the full name */ + an = dns_label_unescape(&p, a, sizeof(a)); + if (an < 0) + return an; + + if (an > 0) { + x++; + + /* If there was a first label, try to get the second one */ + bn = dns_label_unescape(&p, b, sizeof(b)); + if (bn < 0) + return bn; + + if (bn > 0) { + x++; + + /* If there was a second label, try to get the third one */ + q = p; + cn = dns_label_unescape(&p, c, sizeof(c)); + if (cn < 0) + return cn; + + if (cn > 0) + x++; + } else + cn = 0; + } else + an = 0; + + if (x >= 2 && srv_type_label_is_valid(b, bn)) { + + if (x >= 3 && srv_type_label_is_valid(c, cn)) { + + if (dns_service_name_label_is_valid(a, an)) { + + /* OK, got . . . */ + + name = strndup(a, an); + if (!name) + return -ENOMEM; + + type = new(char, bn+1+cn+1); + if (!type) + return -ENOMEM; + strcpy(stpcpy(stpcpy(type, b), "."), c); + + d = p; + goto finish; + } + + } else if (srv_type_label_is_valid(a, an)) { + + /* OK, got . . */ + + name = NULL; + + type = new(char, an+1+bn+1); + if (!type) + return -ENOMEM; + strcpy(stpcpy(stpcpy(type, a), "."), b); + + d = q; + goto finish; + } + } + + name = NULL; + type = NULL; + d = joined; + +finish: + r = dns_name_normalize(d, &domain); + if (r < 0) + return r; + + if (_domain) { + *_domain = domain; + domain = NULL; + } + + if (_type) { + *_type = type; + type = NULL; + } + + if (_name) { + *_name = name; + name = NULL; + } + + return 0; +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 22c394443b..c6d7623e09 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -73,3 +73,6 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len); int dns_srv_type_verify(const char *name); bool dns_service_name_is_valid(const char *name); + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret); +int dns_service_split(const char *joined, char **name, char **type, char **domain); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index a23d5dee05..a664e1c79c 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -332,7 +332,6 @@ static void test_dns_srv_type_verify(void) { assert_se(dns_srv_type_verify("_http._tcp") > 0); assert_se(dns_srv_type_verify("_foo-bar._tcp") > 0); assert_se(dns_srv_type_verify("_w._udp") > 0); - assert_se(dns_srv_type_verify("_piep._sub._w._udp") > 0); assert_se(dns_srv_type_verify("_a800._tcp") > 0); assert_se(dns_srv_type_verify("_a-800._tcp") > 0); @@ -348,6 +347,65 @@ static void test_dns_srv_type_verify(void) { assert_se(dns_srv_type_verify("_800._tcp") == 0); assert_se(dns_srv_type_verify("_-800._tcp") == 0); assert_se(dns_srv_type_verify("_-foo._tcp") == 0); + assert_se(dns_srv_type_verify("_piep._foo._udp") == 0); +} + +static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) { + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + assert_se(dns_service_join(a, b, c, &t) == r); + assert_se(streq_ptr(t, d)); + + if (r < 0) + return; + + assert_se(dns_service_split(t, &x, &y, &z) >= 0); + assert_se(streq_ptr(a, x)); + assert_se(streq_ptr(b, y)); + assert_se(streq_ptr(c, z)); +} + +static void test_dns_service_join(void) { + test_dns_service_join_one("", "", "", -EINVAL, NULL); + test_dns_service_join_one("", "_http._tcp", "", -EINVAL, NULL); + test_dns_service_join_one("", "_http._tcp", "foo", -EINVAL, NULL); + test_dns_service_join_one("foo", "", "foo", -EINVAL, NULL); + test_dns_service_join_one("foo", "foo", "foo", -EINVAL, NULL); + + test_dns_service_join_one("foo", "_http._tcp", "", 0, "foo._http._tcp"); + test_dns_service_join_one(NULL, "_http._tcp", "", 0, "_http._tcp"); + test_dns_service_join_one("foo", "_http._tcp", "foo", 0, "foo._http._tcp.foo"); + test_dns_service_join_one(NULL, "_http._tcp", "foo", 0, "_http._tcp.foo"); + test_dns_service_join_one("Lennart's PC", "_pc._tcp", "foo.bar.com", 0, "Lennart\\039s\\032PC._pc._tcp.foo.bar.com"); + test_dns_service_join_one(NULL, "_pc._tcp", "foo.bar.com", 0, "_pc._tcp.foo.bar.com"); +} + +static void test_dns_service_split_one(const char *joined, const char *a, const char *b, const char *c, int r) { + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + assert_se(dns_service_split(joined, &x, &y, &z) == r); + assert_se(streq_ptr(x, a)); + assert_se(streq_ptr(y, b)); + assert_se(streq_ptr(z, c)); + + if (r < 0) + return; + + if (y) { + assert_se(dns_service_join(x, y, z, &t) == 0); + assert_se(streq_ptr(joined, t)); + } else + assert_se(!x && streq_ptr(z, joined)); +} + +static void test_dns_service_split(void) { + test_dns_service_split_one("", NULL, NULL, "", 0); + test_dns_service_split_one("foo", NULL, NULL, "foo", 0); + test_dns_service_split_one("foo.bar", NULL, NULL, "foo.bar", 0); + test_dns_service_split_one("_foo.bar", NULL, NULL, "_foo.bar", 0); + test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", "", 0); + test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", "", 0); + test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0); } int main(int argc, char *argv[]) { @@ -367,6 +425,8 @@ int main(int argc, char *argv[]) { test_dns_name_to_wire_format(); test_dns_service_name_is_valid(); test_dns_srv_type_verify(); + test_dns_service_join(); + test_dns_service_split(); return 0; } -- cgit v1.2.3-54-g00ecf