]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/dns-domain.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[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
4ad7f276 27#include "dns-domain.h"
6bedfcbb
LP
28#include "parse-util.h"
29#include "string-util.h"
74b2466e
LP
30
31int dns_label_unescape(const char **name, char *dest, size_t sz) {
32 const char *n;
33 char *d;
34 int r = 0;
35
36 assert(name);
37 assert(*name);
38 assert(dest);
39
40 n = *name;
41 d = dest;
42
43 for (;;) {
44 if (*n == '.') {
45 n++;
46 break;
47 }
48
49 if (*n == 0)
50 break;
51
52 if (sz <= 0)
53 return -ENOSPC;
54
1fa65c59
LP
55 if (r >= DNS_LABEL_MAX)
56 return -EINVAL;
57
74b2466e
LP
58 if (*n == '\\') {
59 /* Escaped character */
60
61 n++;
62
63 if (*n == 0)
64 /* Ending NUL */
65 return -EINVAL;
66
67 else if (*n == '\\' || *n == '.') {
68 /* Escaped backslash or dot */
69 *(d++) = *(n++);
70 sz--;
71 r++;
72
73 } else if (n[0] >= '0' && n[0] <= '9') {
74 unsigned k;
75
76 /* Escaped literal ASCII character */
77
78 if (!(n[1] >= '0' && n[1] <= '9') ||
79 !(n[2] >= '0' && n[2] <= '9'))
80 return -EINVAL;
81
82 k = ((unsigned) (n[0] - '0') * 100) +
83 ((unsigned) (n[1] - '0') * 10) +
84 ((unsigned) (n[2] - '0'));
85
86 /* Don't allow CC characters or anything that doesn't fit in 8bit */
87 if (k < ' ' || k > 255 || k == 127)
88 return -EINVAL;
89
90 *(d++) = (char) k;
91 sz--;
92 r++;
93
94 n += 3;
95 } else
96 return -EINVAL;
97
07bed172 98 } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
74b2466e
LP
99
100 /* Normal character */
101 *(d++) = *(n++);
102 sz--;
103 r++;
104 } else
105 return -EINVAL;
106 }
107
108 /* Empty label that is not at the end? */
109 if (r == 0 && *n)
110 return -EINVAL;
111
112 if (sz >= 1)
113 *d = 0;
114
115 *name = n;
116 return r;
117}
118
642900d3
TG
119/* @label_terminal: terminal character of a label, updated to point to the terminal character of
120 * the previous label (always skipping one dot) or to NULL if there are no more
121 * labels. */
122int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
123 const char *terminal;
124 int r;
125
126 assert(name);
127 assert(label_terminal);
128 assert(dest);
129
130 /* no more labels */
131 if (!*label_terminal) {
132 if (sz >= 1)
133 *dest = 0;
134
135 return 0;
136 }
137
138 assert(**label_terminal == '.' || **label_terminal == 0);
139
140 /* skip current terminal character */
141 terminal = *label_terminal - 1;
142
143 /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
144 for (;;) {
145 if (terminal < name) {
146 /* reached the first label, so indicate that there are no more */
147 terminal = NULL;
148 break;
149 }
150
151 /* find the start of the last label */
152 if (*terminal == '.') {
153 const char *y;
154 unsigned slashes = 0;
155
156 for (y = terminal - 1; y >= name && *y == '\\'; y--)
157 slashes ++;
158
159 if (slashes % 2 == 0) {
160 /* the '.' was not escaped */
161 name = terminal + 1;
162 break;
163 } else {
164 terminal = y;
165 continue;
166 }
167 }
168
169 terminal --;
170 }
171
172 r = dns_label_unescape(&name, dest, sz);
173 if (r < 0)
174 return r;
175
176 *label_terminal = terminal;
177
178 return r;
179}
180
74b2466e
LP
181int dns_label_escape(const char *p, size_t l, char **ret) {
182 _cleanup_free_ char *s = NULL;
183 char *q;
184 int r;
185
186 assert(p);
187 assert(ret);
188
1fa65c59
LP
189 if (l > DNS_LABEL_MAX)
190 return -EINVAL;
191
74b2466e
LP
192 s = malloc(l * 4 + 1);
193 if (!s)
194 return -ENOMEM;
195
196 q = s;
197 while (l > 0) {
198
199 if (*p == '.' || *p == '\\') {
200
201 /* Dot or backslash */
202 *(q++) = '\\';
203 *(q++) = *p;
204
205 } else if (*p == '_' ||
206 *p == '-' ||
207 (*p >= '0' && *p <= '9') ||
208 (*p >= 'a' && *p <= 'z') ||
209 (*p >= 'A' && *p <= 'Z')) {
210
211 /* Proper character */
212 *(q++) = *p;
07bed172 213 } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
74b2466e
LP
214
215 /* Everything else */
216 *(q++) = '\\';
07bed172
LP
217 *(q++) = '0' + (char) ((uint8_t) *p / 100);
218 *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
219 *(q++) = '0' + (char) ((uint8_t) *p % 10);
74b2466e
LP
220
221 } else
222 return -EINVAL;
223
224 p++;
225 l--;
226 }
227
228 *q = 0;
229 *ret = s;
230 r = q - s;
231 s = NULL;
232
233 return r;
234}
235
bdf10b5b
LP
236int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
237#ifdef HAVE_LIBIDN
238 _cleanup_free_ uint32_t *input = NULL;
239 size_t input_size;
240 const char *p;
241 bool contains_8bit = false;
242
243 assert(encoded);
244 assert(decoded);
245 assert(decoded_max >= DNS_LABEL_MAX);
246
247 if (encoded_size <= 0)
248 return 0;
249
250 for (p = encoded; p < encoded + encoded_size; p++)
251 if ((uint8_t) *p > 127)
252 contains_8bit = true;
253
254 if (!contains_8bit)
255 return 0;
256
257 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
258 if (!input)
259 return -ENOMEM;
260
261 if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
262 return -EINVAL;
263
264 return strlen(decoded);
265#else
266 return 0;
267#endif
268}
269
270int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
271#ifdef HAVE_LIBIDN
272 size_t input_size, output_size;
273 _cleanup_free_ uint32_t *input = NULL;
274 _cleanup_free_ char *result = NULL;
275 uint32_t *output = NULL;
276 size_t w;
277
278 /* To be invoked after unescaping */
279
280 assert(encoded);
281 assert(decoded);
282
283 if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
284 return 0;
285
286 if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
287 return 0;
288
289 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
290 if (!input)
291 return -ENOMEM;
292
293 output_size = input_size;
294 output = newa(uint32_t, output_size);
295
296 idna_to_unicode_44i(input, input_size, output, &output_size, 0);
297
298 result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
299 if (!result)
300 return -ENOMEM;
301 if (w <= 0)
302 return 0;
303 if (w+1 > decoded_max)
304 return -EINVAL;
305
306 memcpy(decoded, result, w+1);
307 return w;
308#else
309 return 0;
310#endif
311}
312
9ca45586 313int dns_name_concat(const char *a, const char *b, char **_ret) {
74b2466e
LP
314 _cleanup_free_ char *ret = NULL;
315 size_t n = 0, allocated = 0;
9ca45586 316 const char *p = a;
74b2466e
LP
317 bool first = true;
318 int r;
319
9ca45586 320 assert(a);
74b2466e
LP
321
322 for (;;) {
323 _cleanup_free_ char *t = NULL;
324 char label[DNS_LABEL_MAX];
bdf10b5b 325 int k;
74b2466e
LP
326
327 r = dns_label_unescape(&p, label, sizeof(label));
328 if (r < 0)
329 return r;
330 if (r == 0) {
331 if (*p != 0)
332 return -EINVAL;
9ca45586
LP
333
334 if (b) {
335 /* Now continue with the second string, if there is one */
336 p = b;
337 b = NULL;
338 continue;
339 }
340
74b2466e
LP
341 break;
342 }
343
bdf10b5b
LP
344 k = dns_label_undo_idna(label, r, label, sizeof(label));
345 if (k < 0)
346 return k;
347 if (k > 0)
348 r = k;
349
74b2466e
LP
350 r = dns_label_escape(label, r, &t);
351 if (r < 0)
352 return r;
353
9ca45586
LP
354 if (_ret) {
355 if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
356 return -ENOMEM;
74b2466e 357
9ca45586
LP
358 if (!first)
359 ret[n++] = '.';
360 else
361 first = false;
362
363 memcpy(ret + n, t, r);
364 }
74b2466e 365
74b2466e
LP
366 n += r;
367 }
368
76f468c8
LP
369 if (n > DNS_NAME_MAX)
370 return -EINVAL;
371
7b9f7afc 372 if (_ret) {
9ca45586
LP
373 if (!GREEDY_REALLOC(ret, allocated, n + 1))
374 return -ENOMEM;
375
376 ret[n] = 0;
7b9f7afc
LP
377 *_ret = ret;
378 ret = NULL;
379 }
74b2466e
LP
380
381 return 0;
382}
383
b826ab58 384void dns_name_hash_func(const void *s, struct siphash *state) {
74b2466e 385 const char *p = s;
74b2466e
LP
386 int r;
387
388 assert(p);
389
390 while (*p) {
391 char label[DNS_LABEL_MAX+1];
bdf10b5b 392 int k;
74b2466e
LP
393
394 r = dns_label_unescape(&p, label, sizeof(label));
395 if (r < 0)
396 break;
397
bdf10b5b
LP
398 k = dns_label_undo_idna(label, r, label, sizeof(label));
399 if (k < 0)
7da40fc1 400 break;
bdf10b5b
LP
401 if (k > 0)
402 r = k;
403
1e2527a6
TG
404 if (r == 0)
405 break;
406
74b2466e
LP
407 label[r] = 0;
408 ascii_strlower(label);
409
b826ab58 410 string_hash_func(label, state);
74b2466e 411 }
1e2527a6
TG
412
413 /* enforce that all names are terminated by the empty label */
414 string_hash_func("", state);
74b2466e
LP
415}
416
417int dns_name_compare_func(const void *a, const void *b) {
5dfd7011 418 const char *x, *y;
bdf10b5b 419 int r, q, k, w;
74b2466e
LP
420
421 assert(a);
422 assert(b);
423
5dfd7011
TG
424 x = (const char *) a + strlen(a);
425 y = (const char *) b + strlen(b);
426
74b2466e
LP
427 for (;;) {
428 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
429
5dfd7011 430 if (x == NULL && y == NULL)
74b2466e
LP
431 return 0;
432
5dfd7011
TG
433 r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
434 q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
74b2466e
LP
435 if (r < 0 || q < 0)
436 return r - q;
437
bdf10b5b
LP
438 k = dns_label_undo_idna(la, r, la, sizeof(la));
439 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
440 if (k < 0 || w < 0)
441 return k - w;
442 if (k > 0)
443 r = k;
444 if (w > 0)
445 r = w;
446
74b2466e
LP
447 la[r] = lb[q] = 0;
448 r = strcasecmp(la, lb);
449 if (r != 0)
450 return r;
451 }
452}
453
d5099efc
MS
454const struct hash_ops dns_name_hash_ops = {
455 .hash = dns_name_hash_func,
456 .compare = dns_name_compare_func
457};
458
74b2466e 459int dns_name_equal(const char *x, const char *y) {
bdf10b5b 460 int r, q, k, w;
74b2466e
LP
461
462 assert(x);
463 assert(y);
464
465 for (;;) {
466 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
467
468 if (*x == 0 && *y == 0)
469 return true;
470
471 r = dns_label_unescape(&x, la, sizeof(la));
472 if (r < 0)
473 return r;
474
bdf10b5b
LP
475 k = dns_label_undo_idna(la, r, la, sizeof(la));
476 if (k < 0)
477 return k;
478 if (k > 0)
479 r = k;
480
74b2466e
LP
481 q = dns_label_unescape(&y, lb, sizeof(lb));
482 if (q < 0)
483 return q;
bdf10b5b
LP
484 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
485 if (w < 0)
486 return w;
487 if (w > 0)
488 q = w;
74b2466e
LP
489
490 la[r] = lb[q] = 0;
491 if (strcasecmp(la, lb))
492 return false;
493 }
494}
495
496int dns_name_endswith(const char *name, const char *suffix) {
497 const char *n, *s, *saved_n = NULL;
bdf10b5b 498 int r, q, k, w;
74b2466e
LP
499
500 assert(name);
501 assert(suffix);
502
503 n = name;
504 s = suffix;
505
506 for (;;) {
507 char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
508
509 r = dns_label_unescape(&n, ln, sizeof(ln));
510 if (r < 0)
511 return r;
bdf10b5b
LP
512 k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
513 if (k < 0)
514 return k;
515 if (k > 0)
516 r = k;
74b2466e
LP
517
518 if (!saved_n)
519 saved_n = n;
520
521 q = dns_label_unescape(&s, ls, sizeof(ls));
be754d54
LP
522 if (q < 0)
523 return q;
7da40fc1 524 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
bdf10b5b
LP
525 if (w < 0)
526 return w;
527 if (w > 0)
528 q = w;
74b2466e
LP
529
530 if (r == 0 && q == 0)
531 return true;
532 if (r == 0 && saved_n == n)
533 return false;
534
535 ln[r] = ls[q] = 0;
536
537 if (r != q || strcasecmp(ln, ls)) {
538
539 /* Not the same, let's jump back, and try with the next label again */
540 s = suffix;
541 n = saved_n;
542 saved_n = NULL;
543 }
544 }
545}
546
ae72b22c
TG
547int dns_name_between(const char *a, const char *b, const char *c) {
548 int n;
549
550 /* Determine if b is strictly greater than a and strictly smaller than c.
551 We consider the order of names to be circular, so that if a is
552 strictly greater than c, we consider b to be between them if it is
553 either greater than a or smaller than c. This is how the canonical
554 DNS name order used in NSEC records work. */
555
556 n = dns_name_compare_func(a, c);
557 if (n == 0)
558 return -EINVAL;
559 else if (n < 0)
560 /* a<---b--->c */
561 return dns_name_compare_func(a, b) < 0 &&
562 dns_name_compare_func(b, c) < 0;
563 else
564 /* <--b--c a--b--> */
565 return dns_name_compare_func(b, c) < 0 ||
566 dns_name_compare_func(a, b) < 0;
567}
568
74b2466e
LP
569int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
570 const uint8_t *p;
571 int r;
572
573 assert(a);
574 assert(ret);
575
576 p = (const uint8_t*) a;
577
578 if (family == AF_INET)
579 r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
580 else if (family == AF_INET6)
3fe1169f
LP
581 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",
582 hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
583 hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
584 hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
585 hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
586 hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
587 hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
588 hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
589 hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
74b2466e
LP
590 else
591 return -EAFNOSUPPORT;
592 if (r < 0)
593 return -ENOMEM;
594
595 return 0;
596}
597
b914e211
LP
598int dns_name_address(const char *p, int *family, union in_addr_union *address) {
599 int r;
600
601 assert(p);
602 assert(family);
603 assert(address);
604
605 r = dns_name_endswith(p, "in-addr.arpa");
606 if (r < 0)
607 return r;
608 if (r > 0) {
609 uint8_t a[4];
610 unsigned i;
611
612 for (i = 0; i < ELEMENTSOF(a); i++) {
613 char label[DNS_LABEL_MAX+1];
614
615 r = dns_label_unescape(&p, label, sizeof(label));
616 if (r < 0)
617 return r;
618 if (r == 0)
619 return -EINVAL;
620 if (r > 3)
621 return -EINVAL;
622
623 r = safe_atou8(label, &a[i]);
624 if (r < 0)
625 return r;
626 }
627
628 r = dns_name_equal(p, "in-addr.arpa");
629 if (r <= 0)
630 return r;
631
632 *family = AF_INET;
633 address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
634 ((uint32_t) a[2] << 16) |
635 ((uint32_t) a[1] << 8) |
636 (uint32_t) a[0]);
637
638 return 1;
639 }
640
641 r = dns_name_endswith(p, "ip6.arpa");
642 if (r < 0)
643 return r;
644 if (r > 0) {
645 struct in6_addr a;
646 unsigned i;
647
648 for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
649 char label[DNS_LABEL_MAX+1];
650 int x, y;
651
652 r = dns_label_unescape(&p, label, sizeof(label));
653 if (r <= 0)
654 return r;
655 if (r != 1)
656 return -EINVAL;
657 x = unhexchar(label[0]);
658 if (x < 0)
659 return -EINVAL;
660
661 r = dns_label_unescape(&p, label, sizeof(label));
662 if (r <= 0)
663 return r;
664 if (r != 1)
665 return -EINVAL;
666 y = unhexchar(label[0]);
667 if (y < 0)
668 return -EINVAL;
669
670 a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
671 }
672
673 r = dns_name_equal(p, "ip6.arpa");
674 if (r <= 0)
675 return r;
676
677 *family = AF_INET6;
678 address->in6 = a;
679 return 1;
680 }
681
682 return 0;
683}
684
74b2466e
LP
685int dns_name_root(const char *name) {
686 char label[DNS_LABEL_MAX+1];
687 int r;
688
689 assert(name);
690
691 r = dns_label_unescape(&name, label, sizeof(label));
692 if (r < 0)
693 return r;
694
695 return r == 0 && *name == 0;
696}
697
698int dns_name_single_label(const char *name) {
699 char label[DNS_LABEL_MAX+1];
700 int r;
701
702 assert(name);
703
704 r = dns_label_unescape(&name, label, sizeof(label));
705 if (r < 0)
706 return r;
74b2466e
LP
707 if (r == 0)
708 return 0;
709
710 r = dns_label_unescape(&name, label, sizeof(label));
711 if (r < 0)
712 return r;
713
714 return r == 0 && *name == 0;
715}