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