]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
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
22 #ifdef HAVE_LIBIDN
23 #include <idna.h>
24 #include <stringprep.h>
25 #endif
26
27 #include "dns-domain.h"
28 #include "parse-util.h"
29 #include "string-util.h"
30
31 int 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
55 if (r >= DNS_LABEL_MAX)
56 return -EINVAL;
57
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
98 } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
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
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. */
122 int 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
181 int 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
189 if (l > DNS_LABEL_MAX)
190 return -EINVAL;
191
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;
213 } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
214
215 /* Everything else */
216 *(q++) = '\\';
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);
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
236 int 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
270 int 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
313 int dns_name_concat(const char *a, const char *b, char **_ret) {
314 _cleanup_free_ char *ret = NULL;
315 size_t n = 0, allocated = 0;
316 const char *p = a;
317 bool first = true;
318 int r;
319
320 assert(a);
321
322 for (;;) {
323 _cleanup_free_ char *t = NULL;
324 char label[DNS_LABEL_MAX];
325 int k;
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;
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
341 break;
342 }
343
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
350 r = dns_label_escape(label, r, &t);
351 if (r < 0)
352 return r;
353
354 if (_ret) {
355 if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
356 return -ENOMEM;
357
358 if (!first)
359 ret[n++] = '.';
360 else
361 first = false;
362
363 memcpy(ret + n, t, r);
364 }
365
366 n += r;
367 }
368
369 if (n > DNS_NAME_MAX)
370 return -EINVAL;
371
372 if (_ret) {
373 if (!GREEDY_REALLOC(ret, allocated, n + 1))
374 return -ENOMEM;
375
376 ret[n] = 0;
377 *_ret = ret;
378 ret = NULL;
379 }
380
381 return 0;
382 }
383
384 void dns_name_hash_func(const void *s, struct siphash *state) {
385 const char *p = s;
386 int r;
387
388 assert(p);
389
390 while (*p) {
391 char label[DNS_LABEL_MAX+1];
392 int k;
393
394 r = dns_label_unescape(&p, label, sizeof(label));
395 if (r < 0)
396 break;
397
398 k = dns_label_undo_idna(label, r, label, sizeof(label));
399 if (k < 0)
400 break;
401 if (k > 0)
402 r = k;
403
404 if (r == 0)
405 break;
406
407 label[r] = 0;
408 ascii_strlower(label);
409
410 string_hash_func(label, state);
411 }
412
413 /* enforce that all names are terminated by the empty label */
414 string_hash_func("", state);
415 }
416
417 int dns_name_compare_func(const void *a, const void *b) {
418 const char *x, *y;
419 int r, q, k, w;
420
421 assert(a);
422 assert(b);
423
424 x = (const char *) a + strlen(a);
425 y = (const char *) b + strlen(b);
426
427 for (;;) {
428 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
429
430 if (x == NULL && y == NULL)
431 return 0;
432
433 r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
434 q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
435 if (r < 0 || q < 0)
436 return r - q;
437
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
447 la[r] = lb[q] = 0;
448 r = strcasecmp(la, lb);
449 if (r != 0)
450 return r;
451 }
452 }
453
454 const struct hash_ops dns_name_hash_ops = {
455 .hash = dns_name_hash_func,
456 .compare = dns_name_compare_func
457 };
458
459 int dns_name_equal(const char *x, const char *y) {
460 int r, q, k, w;
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
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
481 q = dns_label_unescape(&y, lb, sizeof(lb));
482 if (q < 0)
483 return q;
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;
489
490 la[r] = lb[q] = 0;
491 if (strcasecmp(la, lb))
492 return false;
493 }
494 }
495
496 int dns_name_endswith(const char *name, const char *suffix) {
497 const char *n, *s, *saved_n = NULL;
498 int r, q, k, w;
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;
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;
517
518 if (!saved_n)
519 saved_n = n;
520
521 q = dns_label_unescape(&s, ls, sizeof(ls));
522 if (q < 0)
523 return q;
524 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
525 if (w < 0)
526 return w;
527 if (w > 0)
528 q = w;
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
547 int 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
569 int 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)
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));
590 else
591 return -EAFNOSUPPORT;
592 if (r < 0)
593 return -ENOMEM;
594
595 return 0;
596 }
597
598 int 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
685 int 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
698 int 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;
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 }