]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dns-domain: add calls to join/split SRV/DNS-SD service domains
authorLennart Poettering <lennart@poettering.net>
Mon, 23 Nov 2015 20:21:13 +0000 (21:21 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 23 Nov 2015 20:31:29 +0000 (21:31 +0100)
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
src/shared/dns-domain.h
src/test/test-dns-domain.c

index 014e0bd70db00b0f7c6592acde54dd522f930895..e08143c3be1e44c0d6e83e644b05dabdb3040af7 100644 (file)
@@ -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> . <type> . <type2> . <domain> */
+
+                                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 <type> . <type2> . <domain> */
+
+                        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;
+}
index 22c394443b61c4c80ed79e2d2d2f7561c75ae425..c6d7623e09dd4c0301ade3f1b2226aa1bbecc13e 100644 (file)
@@ -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);
index a23d5dee05843caa3f8171a68e13a0b755dc752c..a664e1c79cf60a1443eb7487cf9bb1c89d4e2cc3 100644 (file)
@@ -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;
 }