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