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