]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/dns-domain.c
shared: add new dns_name_startswith() call
[thirdparty/systemd.git] / src / shared / dns-domain.c
CommitLineData
74b2466e
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
bdf10b5b
LP
22#ifdef HAVE_LIBIDN
23#include <idna.h>
24#include <stringprep.h>
25#endif
26
a8fbdf54
TA
27#include <endian.h>
28#include <netinet/in.h>
29#include <stdio.h>
30#include <string.h>
31#include <sys/socket.h>
32
b5efdb8a 33#include "alloc-util.h"
4ad7f276 34#include "dns-domain.h"
a8fbdf54 35#include "hashmap.h"
e4e73a63 36#include "hexdecoct.h"
a8fbdf54
TA
37#include "in-addr-util.h"
38#include "macro.h"
6bedfcbb
LP
39#include "parse-util.h"
40#include "string-util.h"
dc477e73 41#include "strv.h"
0a49b6b6 42#include "utf8.h"
74b2466e
LP
43
44int dns_label_unescape(const char **name, char *dest, size_t sz) {
45 const char *n;
46 char *d;
47 int r = 0;
48
49 assert(name);
50 assert(*name);
74b2466e
LP
51
52 n = *name;
53 d = dest;
54
55 for (;;) {
56 if (*n == '.') {
57 n++;
58 break;
59 }
60
61 if (*n == 0)
62 break;
63
1fa65c59
LP
64 if (r >= DNS_LABEL_MAX)
65 return -EINVAL;
66
37ade128
LP
67 if (sz <= 0)
68 return -ENOBUFS;
69
74b2466e
LP
70 if (*n == '\\') {
71 /* Escaped character */
72
73 n++;
74
75 if (*n == 0)
76 /* Ending NUL */
77 return -EINVAL;
78
79 else if (*n == '\\' || *n == '.') {
80 /* Escaped backslash or dot */
f7455baa
LP
81
82 if (d)
83 *(d++) = *n;
74b2466e
LP
84 sz--;
85 r++;
f7455baa 86 n++;
74b2466e
LP
87
88 } else if (n[0] >= '0' && n[0] <= '9') {
89 unsigned k;
90
91 /* Escaped literal ASCII character */
92
93 if (!(n[1] >= '0' && n[1] <= '9') ||
94 !(n[2] >= '0' && n[2] <= '9'))
95 return -EINVAL;
96
97 k = ((unsigned) (n[0] - '0') * 100) +
98 ((unsigned) (n[1] - '0') * 10) +
99 ((unsigned) (n[2] - '0'));
100
c7feab76
LP
101 /* Don't allow anything that doesn't
102 * fit in 8bit. Note that we do allow
103 * control characters, as some servers
104 * (e.g. cloudflare) are happy to
105 * generate labels with them
106 * inside. */
107 if (k > 255)
74b2466e
LP
108 return -EINVAL;
109
f7455baa
LP
110 if (d)
111 *(d++) = (char) k;
74b2466e
LP
112 sz--;
113 r++;
114
115 n += 3;
116 } else
117 return -EINVAL;
118
07bed172 119 } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
74b2466e
LP
120
121 /* Normal character */
f7455baa
LP
122
123 if (d)
124 *(d++) = *n;
74b2466e
LP
125 sz--;
126 r++;
f7455baa 127 n++;
74b2466e
LP
128 } else
129 return -EINVAL;
130 }
131
132 /* Empty label that is not at the end? */
133 if (r == 0 && *n)
134 return -EINVAL;
135
f7455baa 136 if (sz >= 1 && d)
74b2466e
LP
137 *d = 0;
138
139 *name = n;
140 return r;
141}
142
642900d3
TG
143/* @label_terminal: terminal character of a label, updated to point to the terminal character of
144 * the previous label (always skipping one dot) or to NULL if there are no more
145 * labels. */
146int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
147 const char *terminal;
148 int r;
149
150 assert(name);
151 assert(label_terminal);
152 assert(dest);
153
154 /* no more labels */
155 if (!*label_terminal) {
156 if (sz >= 1)
157 *dest = 0;
158
159 return 0;
160 }
161
56512859
LP
162 terminal = *label_terminal;
163 assert(*terminal == '.' || *terminal == 0);
642900d3 164
56512859
LP
165 /* Skip current terminal character (and accept domain names ending it ".") */
166 if (*terminal == 0)
167 terminal--;
168 if (terminal >= name && *terminal == '.')
169 terminal--;
642900d3 170
56512859 171 /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
642900d3
TG
172 for (;;) {
173 if (terminal < name) {
56512859 174 /* Reached the first label, so indicate that there are no more */
642900d3
TG
175 terminal = NULL;
176 break;
177 }
178
56512859 179 /* Find the start of the last label */
642900d3
TG
180 if (*terminal == '.') {
181 const char *y;
182 unsigned slashes = 0;
183
184 for (y = terminal - 1; y >= name && *y == '\\'; y--)
185 slashes ++;
186
187 if (slashes % 2 == 0) {
56512859 188 /* The '.' was not escaped */
642900d3
TG
189 name = terminal + 1;
190 break;
191 } else {
192 terminal = y;
193 continue;
194 }
195 }
196
197 terminal --;
198 }
199
200 r = dns_label_unescape(&name, dest, sz);
201 if (r < 0)
202 return r;
203
204 *label_terminal = terminal;
205
206 return r;
207}
208
422baca0 209int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
74b2466e 210 char *q;
74b2466e 211
3b37fa73
LP
212 /* DNS labels must be between 1 and 63 characters long. A
213 * zero-length label does not exist. See RFC 2182, Section
214 * 11. */
215
216 if (l <= 0 || l > DNS_LABEL_MAX)
1fa65c59 217 return -EINVAL;
422baca0 218 if (sz < 1)
37ade128 219 return -ENOBUFS;
1fa65c59 220
422baca0
LP
221 assert(p);
222 assert(dest);
74b2466e 223
422baca0 224 q = dest;
74b2466e
LP
225 while (l > 0) {
226
227 if (*p == '.' || *p == '\\') {
228
3b37fa73
LP
229 /* Dot or backslash */
230
422baca0 231 if (sz < 3)
37ade128 232 return -ENOBUFS;
422baca0 233
74b2466e
LP
234 *(q++) = '\\';
235 *(q++) = *p;
236
422baca0
LP
237 sz -= 2;
238
74b2466e
LP
239 } else if (*p == '_' ||
240 *p == '-' ||
241 (*p >= '0' && *p <= '9') ||
242 (*p >= 'a' && *p <= 'z') ||
243 (*p >= 'A' && *p <= 'Z')) {
244
245 /* Proper character */
422baca0
LP
246
247 if (sz < 2)
37ade128 248 return -ENOBUFS;
422baca0 249
74b2466e 250 *(q++) = *p;
422baca0
LP
251 sz -= 1;
252
c7feab76 253 } else {
74b2466e
LP
254
255 /* Everything else */
422baca0
LP
256
257 if (sz < 5)
37ade128 258 return -ENOBUFS;
422baca0 259
74b2466e 260 *(q++) = '\\';
07bed172
LP
261 *(q++) = '0' + (char) ((uint8_t) *p / 100);
262 *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
263 *(q++) = '0' + (char) ((uint8_t) *p % 10);
74b2466e 264
422baca0 265 sz -= 4;
c7feab76 266 }
74b2466e
LP
267
268 p++;
269 l--;
270 }
271
272 *q = 0;
422baca0
LP
273 return (int) (q - dest);
274}
275
276int dns_label_escape_new(const char *p, size_t l, char **ret) {
277 _cleanup_free_ char *s = NULL;
278 int r;
279
280 assert(p);
281 assert(ret);
282
3b37fa73 283 if (l <= 0 || l > DNS_LABEL_MAX)
422baca0
LP
284 return -EINVAL;
285
286 s = new(char, DNS_LABEL_ESCAPED_MAX);
287 if (!s)
288 return -ENOMEM;
289
290 r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX);
291 if (r < 0)
292 return r;
293
74b2466e 294 *ret = s;
74b2466e
LP
295 s = NULL;
296
297 return r;
298}
299
bdf10b5b
LP
300int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
301#ifdef HAVE_LIBIDN
302 _cleanup_free_ uint32_t *input = NULL;
3b37fa73 303 size_t input_size, l;
bdf10b5b
LP
304 const char *p;
305 bool contains_8bit = false;
3b37fa73 306 char buffer[DNS_LABEL_MAX+1];
bdf10b5b
LP
307
308 assert(encoded);
309 assert(decoded);
3b37fa73
LP
310
311 /* Converts an U-label into an A-label */
bdf10b5b
LP
312
313 if (encoded_size <= 0)
3b37fa73 314 return -EINVAL;
bdf10b5b
LP
315
316 for (p = encoded; p < encoded + encoded_size; p++)
317 if ((uint8_t) *p > 127)
318 contains_8bit = true;
319
3b37fa73
LP
320 if (!contains_8bit) {
321 if (encoded_size > DNS_LABEL_MAX)
322 return -EINVAL;
323
bdf10b5b 324 return 0;
3b37fa73 325 }
bdf10b5b
LP
326
327 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
328 if (!input)
329 return -ENOMEM;
330
3b37fa73
LP
331 if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
332 return -EINVAL;
333
334 l = strlen(buffer);
335
336 /* Verify that the the result is not longer than one DNS label. */
337 if (l <= 0 || l > DNS_LABEL_MAX)
bdf10b5b 338 return -EINVAL;
3b37fa73 339 if (l > decoded_max)
37ade128 340 return -ENOBUFS;
3b37fa73
LP
341
342 memcpy(decoded, buffer, l);
bdf10b5b 343
3b37fa73
LP
344 /* If there's room, append a trailing NUL byte, but only then */
345 if (decoded_max > l)
346 decoded[l] = 0;
347
348 return (int) l;
bdf10b5b
LP
349#else
350 return 0;
351#endif
352}
353
354int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
355#ifdef HAVE_LIBIDN
356 size_t input_size, output_size;
357 _cleanup_free_ uint32_t *input = NULL;
358 _cleanup_free_ char *result = NULL;
359 uint32_t *output = NULL;
360 size_t w;
361
3b37fa73 362 /* To be invoked after unescaping. Converts an A-label into an U-label. */
bdf10b5b
LP
363
364 assert(encoded);
365 assert(decoded);
366
3b37fa73
LP
367 if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
368 return -EINVAL;
369
bdf10b5b
LP
370 if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
371 return 0;
372
373 if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
374 return 0;
375
376 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
377 if (!input)
378 return -ENOMEM;
379
380 output_size = input_size;
381 output = newa(uint32_t, output_size);
382
383 idna_to_unicode_44i(input, input_size, output, &output_size, 0);
384
385 result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
386 if (!result)
387 return -ENOMEM;
388 if (w <= 0)
bdf10b5b 389 return -EINVAL;
3b37fa73 390 if (w > decoded_max)
37ade128 391 return -ENOBUFS;
3b37fa73
LP
392
393 memcpy(decoded, result, w);
394
395 /* Append trailing NUL byte if there's space, but only then. */
396 if (decoded_max > w)
397 decoded[w] = 0;
bdf10b5b 398
bdf10b5b
LP
399 return w;
400#else
401 return 0;
402#endif
403}
404
9ca45586 405int dns_name_concat(const char *a, const char *b, char **_ret) {
74b2466e
LP
406 _cleanup_free_ char *ret = NULL;
407 size_t n = 0, allocated = 0;
9ca45586 408 const char *p = a;
74b2466e
LP
409 bool first = true;
410 int r;
411
9ca45586 412 assert(a);
74b2466e
LP
413
414 for (;;) {
74b2466e 415 char label[DNS_LABEL_MAX];
bdf10b5b 416 int k;
74b2466e
LP
417
418 r = dns_label_unescape(&p, label, sizeof(label));
419 if (r < 0)
420 return r;
421 if (r == 0) {
422 if (*p != 0)
423 return -EINVAL;
9ca45586
LP
424
425 if (b) {
426 /* Now continue with the second string, if there is one */
427 p = b;
428 b = NULL;
429 continue;
430 }
431
74b2466e
LP
432 break;
433 }
434
bdf10b5b
LP
435 k = dns_label_undo_idna(label, r, label, sizeof(label));
436 if (k < 0)
437 return k;
438 if (k > 0)
439 r = k;
440
9ca45586 441 if (_ret) {
422baca0 442 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
9ca45586 443 return -ENOMEM;
74b2466e 444
422baca0
LP
445 r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX);
446 if (r < 0)
447 return r;
448
9ca45586 449 if (!first)
422baca0
LP
450 ret[n] = '.';
451 } else {
452 char escaped[DNS_LABEL_ESCAPED_MAX];
9ca45586 453
422baca0
LP
454 r = dns_label_escape(label, r, escaped, sizeof(escaped));
455 if (r < 0)
456 return r;
9ca45586 457 }
74b2466e 458
422baca0
LP
459 if (!first)
460 n++;
461 else
462 first = false;
463
74b2466e
LP
464 n += r;
465 }
466
1dfbf000
LP
467 if (n > DNS_HOSTNAME_MAX)
468 return -EINVAL;
469
7b9f7afc 470 if (_ret) {
9ca45586
LP
471 if (!GREEDY_REALLOC(ret, allocated, n + 1))
472 return -ENOMEM;
473
474 ret[n] = 0;
7b9f7afc
LP
475 *_ret = ret;
476 ret = NULL;
477 }
74b2466e
LP
478
479 return 0;
480}
481
b826ab58 482void dns_name_hash_func(const void *s, struct siphash *state) {
74b2466e 483 const char *p = s;
74b2466e
LP
484 int r;
485
486 assert(p);
487
d12315a4 488 for (;;) {
74b2466e 489 char label[DNS_LABEL_MAX+1];
bdf10b5b 490 int k;
74b2466e
LP
491
492 r = dns_label_unescape(&p, label, sizeof(label));
493 if (r < 0)
494 break;
d12315a4
LP
495 if (r == 0)
496 break;
74b2466e 497
bdf10b5b
LP
498 k = dns_label_undo_idna(label, r, label, sizeof(label));
499 if (k < 0)
7da40fc1 500 break;
bdf10b5b
LP
501 if (k > 0)
502 r = k;
503
509eddd2
LP
504 ascii_strlower_n(label, r);
505 siphash24_compress(label, r, state);
d5115566 506 siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
74b2466e 507 }
1e2527a6
TG
508
509 /* enforce that all names are terminated by the empty label */
510 string_hash_func("", state);
74b2466e
LP
511}
512
513int dns_name_compare_func(const void *a, const void *b) {
5dfd7011 514 const char *x, *y;
bdf10b5b 515 int r, q, k, w;
74b2466e
LP
516
517 assert(a);
518 assert(b);
519
5dfd7011
TG
520 x = (const char *) a + strlen(a);
521 y = (const char *) b + strlen(b);
522
74b2466e
LP
523 for (;;) {
524 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
525
5dfd7011 526 if (x == NULL && y == NULL)
74b2466e
LP
527 return 0;
528
5dfd7011
TG
529 r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
530 q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
74b2466e
LP
531 if (r < 0 || q < 0)
532 return r - q;
533
bdf10b5b
LP
534 k = dns_label_undo_idna(la, r, la, sizeof(la));
535 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
536 if (k < 0 || w < 0)
537 return k - w;
538 if (k > 0)
539 r = k;
540 if (w > 0)
735323d9 541 q = w;
bdf10b5b 542
74b2466e
LP
543 la[r] = lb[q] = 0;
544 r = strcasecmp(la, lb);
545 if (r != 0)
546 return r;
547 }
548}
549
d5099efc
MS
550const struct hash_ops dns_name_hash_ops = {
551 .hash = dns_name_hash_func,
552 .compare = dns_name_compare_func
553};
554
74b2466e 555int dns_name_equal(const char *x, const char *y) {
bdf10b5b 556 int r, q, k, w;
74b2466e
LP
557
558 assert(x);
559 assert(y);
560
561 for (;;) {
562 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
563
564 if (*x == 0 && *y == 0)
565 return true;
566
567 r = dns_label_unescape(&x, la, sizeof(la));
568 if (r < 0)
569 return r;
3b37fa73
LP
570 if (r > 0) {
571 k = dns_label_undo_idna(la, r, la, sizeof(la));
572 if (k < 0)
573 return k;
574 if (k > 0)
575 r = k;
576 }
bdf10b5b 577
74b2466e
LP
578 q = dns_label_unescape(&y, lb, sizeof(lb));
579 if (q < 0)
580 return q;
3b37fa73
LP
581 if (q > 0) {
582 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
583 if (w < 0)
584 return w;
585 if (w > 0)
586 q = w;
587 }
588
589 /* If one name had fewer labels than the other, this
590 * will show up as empty label here, which the
591 * strcasecmp() below will properly consider different
592 * from a non-empty label. */
74b2466e
LP
593
594 la[r] = lb[q] = 0;
3b37fa73 595 if (strcasecmp(la, lb) != 0)
74b2466e
LP
596 return false;
597 }
598}
599
600int dns_name_endswith(const char *name, const char *suffix) {
601 const char *n, *s, *saved_n = NULL;
bdf10b5b 602 int r, q, k, w;
74b2466e
LP
603
604 assert(name);
605 assert(suffix);
606
607 n = name;
608 s = suffix;
609
610 for (;;) {
611 char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
612
613 r = dns_label_unescape(&n, ln, sizeof(ln));
614 if (r < 0)
615 return r;
3b37fa73
LP
616 if (r > 0) {
617 k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
618 if (k < 0)
619 return k;
620 if (k > 0)
621 r = k;
622 }
74b2466e
LP
623
624 if (!saved_n)
625 saved_n = n;
626
627 q = dns_label_unescape(&s, ls, sizeof(ls));
be754d54
LP
628 if (q < 0)
629 return q;
3b37fa73
LP
630 if (q > 0) {
631 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
632 if (w < 0)
633 return w;
634 if (w > 0)
635 q = w;
636 }
74b2466e
LP
637
638 if (r == 0 && q == 0)
639 return true;
640 if (r == 0 && saved_n == n)
641 return false;
642
643 ln[r] = ls[q] = 0;
644
645 if (r != q || strcasecmp(ln, ls)) {
646
647 /* Not the same, let's jump back, and try with the next label again */
648 s = suffix;
649 n = saved_n;
650 saved_n = NULL;
651 }
652 }
653}
654
eb241cdb
LP
655static int dns_label_unescape_undo_idna(const char **name, char *dest, size_t sz) {
656 int r, k;
657
658 /* Clobbers all arguments on failure... */
659
660 r = dns_label_unescape(name, dest, sz);
661 if (r <= 0)
662 return r;
663
664 k = dns_label_undo_idna(dest, r, dest, sz);
665 if (k < 0)
666 return k;
667 if (k == 0) /* not an IDNA name */
668 return r;
669
670 return k;
671}
672
673int dns_name_startswith(const char *name, const char *prefix) {
674 const char *n, *p;
675 int r, q;
676
677 assert(name);
678 assert(prefix);
679
680 n = name;
681 p = prefix;
682
683 for (;;) {
684 char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
685
686 r = dns_label_unescape_undo_idna(&p, lp, sizeof(lp));
687 if (r < 0)
688 return r;
689 if (r == 0)
690 return true;
691
692 q = dns_label_unescape_undo_idna(&n, ln, sizeof(ln));
693 if (q < 0)
694 return q;
695
696 if (r != q)
697 return false;
698 if (ascii_strcasecmp_n(ln, lp, r) != 0)
699 return false;
700 }
701}
702
58db254a
LP
703int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
704 const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
705 int r, q, k, w;
706
707 assert(name);
708 assert(old_suffix);
709 assert(new_suffix);
710 assert(ret);
711
712 n = name;
713 s = old_suffix;
714
715 for (;;) {
716 char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
717
718 if (!saved_before)
719 saved_before = n;
720
721 r = dns_label_unescape(&n, ln, sizeof(ln));
722 if (r < 0)
723 return r;
3b37fa73
LP
724 if (r > 0) {
725 k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
726 if (k < 0)
727 return k;
728 if (k > 0)
729 r = k;
730 }
58db254a
LP
731
732 if (!saved_after)
733 saved_after = n;
734
735 q = dns_label_unescape(&s, ls, sizeof(ls));
736 if (q < 0)
737 return q;
3b37fa73
LP
738 if (q > 0) {
739 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
740 if (w < 0)
741 return w;
742 if (w > 0)
743 q = w;
744 }
58db254a
LP
745
746 if (r == 0 && q == 0)
747 break;
748 if (r == 0 && saved_after == n) {
749 *ret = NULL; /* doesn't match */
750 return 0;
751 }
752
753 ln[r] = ls[q] = 0;
754
755 if (r != q || strcasecmp(ln, ls)) {
756
757 /* Not the same, let's jump back, and try with the next label again */
758 s = old_suffix;
759 n = saved_after;
760 saved_after = saved_before = NULL;
761 }
762 }
763
764 /* Found it! Now generate the new name */
765 prefix = strndupa(name, saved_before - name);
766
767 r = dns_name_concat(prefix, new_suffix, ret);
768 if (r < 0)
769 return r;
770
771 return 1;
772}
773
ae72b22c
TG
774int dns_name_between(const char *a, const char *b, const char *c) {
775 int n;
776
777 /* Determine if b is strictly greater than a and strictly smaller than c.
778 We consider the order of names to be circular, so that if a is
779 strictly greater than c, we consider b to be between them if it is
780 either greater than a or smaller than c. This is how the canonical
781 DNS name order used in NSEC records work. */
782
783 n = dns_name_compare_func(a, c);
784 if (n == 0)
785 return -EINVAL;
786 else if (n < 0)
787 /* a<---b--->c */
788 return dns_name_compare_func(a, b) < 0 &&
789 dns_name_compare_func(b, c) < 0;
790 else
791 /* <--b--c a--b--> */
792 return dns_name_compare_func(b, c) < 0 ||
793 dns_name_compare_func(a, b) < 0;
794}
795
74b2466e
LP
796int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
797 const uint8_t *p;
798 int r;
799
800 assert(a);
801 assert(ret);
802
803 p = (const uint8_t*) a;
804
805 if (family == AF_INET)
806 r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
807 else if (family == AF_INET6)
3fe1169f
LP
808 r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa",
809 hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
810 hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
811 hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
812 hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
813 hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
814 hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
815 hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
816 hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
74b2466e
LP
817 else
818 return -EAFNOSUPPORT;
819 if (r < 0)
820 return -ENOMEM;
821
822 return 0;
823}
824
b914e211
LP
825int dns_name_address(const char *p, int *family, union in_addr_union *address) {
826 int r;
827
828 assert(p);
829 assert(family);
830 assert(address);
831
832 r = dns_name_endswith(p, "in-addr.arpa");
833 if (r < 0)
834 return r;
835 if (r > 0) {
836 uint8_t a[4];
837 unsigned i;
838
839 for (i = 0; i < ELEMENTSOF(a); i++) {
840 char label[DNS_LABEL_MAX+1];
841
842 r = dns_label_unescape(&p, label, sizeof(label));
843 if (r < 0)
844 return r;
845 if (r == 0)
846 return -EINVAL;
847 if (r > 3)
848 return -EINVAL;
849
850 r = safe_atou8(label, &a[i]);
851 if (r < 0)
852 return r;
853 }
854
855 r = dns_name_equal(p, "in-addr.arpa");
856 if (r <= 0)
857 return r;
858
859 *family = AF_INET;
860 address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
861 ((uint32_t) a[2] << 16) |
862 ((uint32_t) a[1] << 8) |
863 (uint32_t) a[0]);
864
865 return 1;
866 }
867
868 r = dns_name_endswith(p, "ip6.arpa");
869 if (r < 0)
870 return r;
871 if (r > 0) {
872 struct in6_addr a;
873 unsigned i;
874
875 for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
876 char label[DNS_LABEL_MAX+1];
877 int x, y;
878
879 r = dns_label_unescape(&p, label, sizeof(label));
880 if (r <= 0)
881 return r;
882 if (r != 1)
883 return -EINVAL;
884 x = unhexchar(label[0]);
885 if (x < 0)
886 return -EINVAL;
887
888 r = dns_label_unescape(&p, label, sizeof(label));
889 if (r <= 0)
890 return r;
891 if (r != 1)
892 return -EINVAL;
893 y = unhexchar(label[0]);
894 if (y < 0)
895 return -EINVAL;
896
897 a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
898 }
899
900 r = dns_name_equal(p, "ip6.arpa");
901 if (r <= 0)
902 return r;
903
904 *family = AF_INET6;
905 address->in6 = a;
906 return 1;
907 }
908
909 return 0;
910}
911
dc477e73 912bool dns_name_is_root(const char *name) {
74b2466e
LP
913
914 assert(name);
915
dc477e73
LP
916 /* There are exactly two ways to encode the root domain name:
917 * as empty string, or with a single dot. */
74b2466e 918
dc477e73 919 return STR_IN_SET(name, "", ".");
74b2466e
LP
920}
921
dc477e73 922bool dns_name_is_single_label(const char *name) {
74b2466e
LP
923 char label[DNS_LABEL_MAX+1];
924 int r;
925
926 assert(name);
927
928 r = dns_label_unescape(&name, label, sizeof(label));
dc477e73
LP
929 if (r <= 0)
930 return false;
74b2466e 931
dc477e73 932 return dns_name_is_root(name);
74b2466e 933}
54adabf7 934
50dee79b 935/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
3cd03457 936int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
c6cefd13 937 uint8_t *label_length, *out;
54adabf7
BG
938 int r;
939
c6cefd13
LP
940 assert(domain);
941 assert(buffer);
54adabf7
BG
942
943 out = buffer;
944
945 do {
50dee79b 946 /* Reserve a byte for label length */
c6cefd13 947 if (len <= 0)
54adabf7
BG
948 return -ENOBUFS;
949 len--;
950 label_length = out;
951 out++;
952
50dee79b
LP
953 /* Convert and copy a single label. Note that
954 * dns_label_unescape() returns 0 when it hits the end
955 * of the domain name, which we rely on here to encode
956 * the trailing NUL byte. */
54adabf7
BG
957 r = dns_label_unescape(&domain, (char *) out, len);
958 if (r < 0)
959 return r;
960
b577e3d5
LP
961 /* Optionally, output the name in DNSSEC canonical
962 * format, as described in RFC 4034, section 6.2. Or
963 * in other words: in lower-case. */
964 if (canonical)
965 ascii_strlower_n((char*) out, (size_t) r);
3cd03457 966
50dee79b 967 /* Fill label length, move forward */
54adabf7
BG
968 *label_length = r;
969 out += r;
970 len -= r;
50dee79b 971
54adabf7
BG
972 } while (r != 0);
973
50dee79b
LP
974 /* Verify the maximum size of the encoded name. The trailing
975 * dot + NUL byte account are included this time, hence
976 * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
977 * time. */
978 if (out - buffer > DNS_HOSTNAME_MAX + 2)
979 return -EINVAL;
980
54adabf7
BG
981 return out - buffer;
982}
0a49b6b6 983
0e8eedbb
LP
984static bool srv_type_label_is_valid(const char *label, size_t n) {
985 size_t k;
986
987 assert(label);
988
989 if (n < 2) /* Label needs to be at least 2 chars long */
990 return false;
991
992 if (label[0] != '_') /* First label char needs to be underscore */
993 return false;
994
995 /* Second char must be a letter */
996 if (!(label[1] >= 'A' && label[1] <= 'Z') &&
997 !(label[1] >= 'a' && label[1] <= 'z'))
998 return false;
999
1000 /* Third and further chars must be alphanumeric or a hyphen */
1001 for (k = 2; k < n; k++) {
1002 if (!(label[k] >= 'A' && label[k] <= 'Z') &&
1003 !(label[k] >= 'a' && label[k] <= 'z') &&
1004 !(label[k] >= '0' && label[k] <= '9') &&
1005 label[k] != '-')
1006 return false;
1007 }
1008
1009 return true;
1010}
1011
7e8131e9 1012bool dns_srv_type_is_valid(const char *name) {
0a49b6b6
LP
1013 unsigned c = 0;
1014 int r;
1015
1016 if (!name)
7e8131e9 1017 return false;
0a49b6b6
LP
1018
1019 for (;;) {
1020 char label[DNS_LABEL_MAX];
0a49b6b6
LP
1021
1022 /* This more or less implements RFC 6335, Section 5.1 */
1023
1024 r = dns_label_unescape(&name, label, sizeof(label));
0a49b6b6 1025 if (r < 0)
7e8131e9 1026 return false;
0a49b6b6 1027 if (r == 0)
0e8eedbb 1028 break;
0a49b6b6 1029
0e8eedbb 1030 if (c >= 2)
7e8131e9 1031 return false;
0a49b6b6 1032
0e8eedbb 1033 if (!srv_type_label_is_valid(label, r))
7e8131e9 1034 return false;
0a49b6b6
LP
1035
1036 c++;
1037 }
0e8eedbb
LP
1038
1039 return c == 2; /* exactly two labels */
0a49b6b6
LP
1040}
1041
1042bool dns_service_name_is_valid(const char *name) {
1043 size_t l;
1044
1045 /* This more or less implements RFC 6763, Section 4.1.1 */
1046
1047 if (!name)
1048 return false;
1049
1050 if (!utf8_is_valid(name))
1051 return false;
1052
1053 if (string_has_cc(name, NULL))
1054 return false;
1055
1056 l = strlen(name);
1057 if (l <= 0)
1058 return false;
1059 if (l > 63)
1060 return false;
1061
1062 return true;
1063}
0e8eedbb
LP
1064
1065int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
422baca0
LP
1066 char escaped[DNS_LABEL_ESCAPED_MAX];
1067 _cleanup_free_ char *n = NULL;
0e8eedbb
LP
1068 int r;
1069
1070 assert(type);
1071 assert(domain);
1072 assert(ret);
1073
7e8131e9 1074 if (!dns_srv_type_is_valid(type))
0e8eedbb
LP
1075 return -EINVAL;
1076
1077 if (!name)
1078 return dns_name_concat(type, domain, ret);
1079
1080 if (!dns_service_name_is_valid(name))
1081 return -EINVAL;
1082
422baca0 1083 r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped));
0e8eedbb
LP
1084 if (r < 0)
1085 return r;
1086
1087 r = dns_name_concat(type, domain, &n);
1088 if (r < 0)
1089 return r;
1090
1091 return dns_name_concat(escaped, n, ret);
1092}
1093
1094static bool dns_service_name_label_is_valid(const char *label, size_t n) {
1095 char *s;
1096
1097 assert(label);
1098
1099 if (memchr(label, 0, n))
1100 return false;
1101
1102 s = strndupa(label, n);
1103 return dns_service_name_is_valid(s);
1104}
1105
1106int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
1107 _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
1108 const char *p = joined, *q = NULL, *d = NULL;
1109 char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
1110 int an, bn, cn, r;
1111 unsigned x = 0;
1112
1113 assert(joined);
1114
1115 /* Get first label from the full name */
1116 an = dns_label_unescape(&p, a, sizeof(a));
1117 if (an < 0)
1118 return an;
1119
1120 if (an > 0) {
1121 x++;
1122
1123 /* If there was a first label, try to get the second one */
1124 bn = dns_label_unescape(&p, b, sizeof(b));
1125 if (bn < 0)
1126 return bn;
1127
1128 if (bn > 0) {
1129 x++;
1130
1131 /* If there was a second label, try to get the third one */
1132 q = p;
1133 cn = dns_label_unescape(&p, c, sizeof(c));
1134 if (cn < 0)
1135 return cn;
1136
1137 if (cn > 0)
1138 x++;
1139 } else
1140 cn = 0;
1141 } else
1142 an = 0;
1143
1144 if (x >= 2 && srv_type_label_is_valid(b, bn)) {
1145
1146 if (x >= 3 && srv_type_label_is_valid(c, cn)) {
1147
1148 if (dns_service_name_label_is_valid(a, an)) {
1149
1150 /* OK, got <name> . <type> . <type2> . <domain> */
1151
1152 name = strndup(a, an);
1153 if (!name)
1154 return -ENOMEM;
1155
1156 type = new(char, bn+1+cn+1);
1157 if (!type)
1158 return -ENOMEM;
1159 strcpy(stpcpy(stpcpy(type, b), "."), c);
1160
1161 d = p;
1162 goto finish;
1163 }
1164
1165 } else if (srv_type_label_is_valid(a, an)) {
1166
1167 /* OK, got <type> . <type2> . <domain> */
1168
1169 name = NULL;
1170
1171 type = new(char, an+1+bn+1);
1172 if (!type)
1173 return -ENOMEM;
1174 strcpy(stpcpy(stpcpy(type, a), "."), b);
1175
1176 d = q;
1177 goto finish;
1178 }
1179 }
1180
1181 name = NULL;
1182 type = NULL;
1183 d = joined;
1184
1185finish:
1186 r = dns_name_normalize(d, &domain);
1187 if (r < 0)
1188 return r;
1189
1190 if (_domain) {
1191 *_domain = domain;
1192 domain = NULL;
1193 }
1194
1195 if (_type) {
1196 *_type = type;
1197 type = NULL;
1198 }
1199
1200 if (_name) {
1201 *_name = name;
1202 name = NULL;
1203 }
1204
1205 return 0;
1206}
e7ff0e0b
LP
1207
1208int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
1209 const char* labels[DNS_N_LABELS_MAX+1];
1210 unsigned n = 0;
1211 const char *p;
1212 int r;
1213
1214 assert(name);
1215 assert(ret);
1216
1217 p = name;
1218 for (;;) {
1219 if (n > DNS_N_LABELS_MAX)
1220 return -EINVAL;
1221
1222 labels[n] = p;
1223
1224 r = dns_name_parent(&p);
1225 if (r < 0)
1226 return r;
1227 if (r == 0)
1228 break;
1229
1230 n++;
1231 }
1232
1233 if (n < n_labels)
1234 return -EINVAL;
1235
1236 *ret = labels[n - n_labels];
1237 return (int) (n - n_labels);
1238}
1239
1240int dns_name_count_labels(const char *name) {
1241 unsigned n = 0;
1242 const char *p;
1243 int r;
1244
1245 assert(name);
1246
1247 p = name;
1248 for (;;) {
1249 r = dns_name_parent(&p);
1250 if (r < 0)
1251 return r;
1252 if (r == 0)
1253 break;
1254
1255 if (n >= DNS_N_LABELS_MAX)
1256 return -EINVAL;
1257
1258 n++;
1259 }
1260
1261 return (int) n;
1262}
db5b0e92
LP
1263
1264int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
1265 int r;
1266
1267 assert(a);
1268 assert(b);
1269
1270 while (n_labels > 0) {
1271
1272 r = dns_name_parent(&a);
1273 if (r <= 0)
1274 return r;
1275
1276 n_labels --;
1277 }
1278
1279 return dns_name_equal(a, b);
1280}