]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/dns-domain.c
resolve: move dns routines into shared
[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"
74b2466e
LP
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
1fa65c59
LP
53 if (r >= DNS_LABEL_MAX)
54 return -EINVAL;
55
74b2466e
LP
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
07bed172 96 } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
74b2466e
LP
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
117int dns_label_escape(const char *p, size_t l, char **ret) {
118 _cleanup_free_ char *s = NULL;
119 char *q;
120 int r;
121
122 assert(p);
123 assert(ret);
124
1fa65c59
LP
125 if (l > DNS_LABEL_MAX)
126 return -EINVAL;
127
74b2466e
LP
128 s = malloc(l * 4 + 1);
129 if (!s)
130 return -ENOMEM;
131
132 q = s;
133 while (l > 0) {
134
135 if (*p == '.' || *p == '\\') {
136
137 /* Dot or backslash */
138 *(q++) = '\\';
139 *(q++) = *p;
140
141 } else if (*p == '_' ||
142 *p == '-' ||
143 (*p >= '0' && *p <= '9') ||
144 (*p >= 'a' && *p <= 'z') ||
145 (*p >= 'A' && *p <= 'Z')) {
146
147 /* Proper character */
148 *(q++) = *p;
07bed172 149 } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
74b2466e
LP
150
151 /* Everything else */
152 *(q++) = '\\';
07bed172
LP
153 *(q++) = '0' + (char) ((uint8_t) *p / 100);
154 *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
155 *(q++) = '0' + (char) ((uint8_t) *p % 10);
74b2466e
LP
156
157 } else
158 return -EINVAL;
159
160 p++;
161 l--;
162 }
163
164 *q = 0;
165 *ret = s;
166 r = q - s;
167 s = NULL;
168
169 return r;
170}
171
bdf10b5b
LP
172int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
173#ifdef HAVE_LIBIDN
174 _cleanup_free_ uint32_t *input = NULL;
175 size_t input_size;
176 const char *p;
177 bool contains_8bit = false;
178
179 assert(encoded);
180 assert(decoded);
181 assert(decoded_max >= DNS_LABEL_MAX);
182
183 if (encoded_size <= 0)
184 return 0;
185
186 for (p = encoded; p < encoded + encoded_size; p++)
187 if ((uint8_t) *p > 127)
188 contains_8bit = true;
189
190 if (!contains_8bit)
191 return 0;
192
193 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
194 if (!input)
195 return -ENOMEM;
196
197 if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
198 return -EINVAL;
199
200 return strlen(decoded);
201#else
202 return 0;
203#endif
204}
205
206int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
207#ifdef HAVE_LIBIDN
208 size_t input_size, output_size;
209 _cleanup_free_ uint32_t *input = NULL;
210 _cleanup_free_ char *result = NULL;
211 uint32_t *output = NULL;
212 size_t w;
213
214 /* To be invoked after unescaping */
215
216 assert(encoded);
217 assert(decoded);
218
219 if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
220 return 0;
221
222 if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
223 return 0;
224
225 input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
226 if (!input)
227 return -ENOMEM;
228
229 output_size = input_size;
230 output = newa(uint32_t, output_size);
231
232 idna_to_unicode_44i(input, input_size, output, &output_size, 0);
233
234 result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
235 if (!result)
236 return -ENOMEM;
237 if (w <= 0)
238 return 0;
239 if (w+1 > decoded_max)
240 return -EINVAL;
241
242 memcpy(decoded, result, w+1);
243 return w;
244#else
245 return 0;
246#endif
247}
248
74b2466e
LP
249int dns_name_normalize(const char *s, char **_ret) {
250 _cleanup_free_ char *ret = NULL;
251 size_t n = 0, allocated = 0;
252 const char *p = s;
253 bool first = true;
254 int r;
255
256 assert(s);
74b2466e
LP
257
258 for (;;) {
259 _cleanup_free_ char *t = NULL;
260 char label[DNS_LABEL_MAX];
bdf10b5b 261 int k;
74b2466e
LP
262
263 r = dns_label_unescape(&p, label, sizeof(label));
264 if (r < 0)
265 return r;
266 if (r == 0) {
267 if (*p != 0)
268 return -EINVAL;
269 break;
270 }
271
bdf10b5b
LP
272 k = dns_label_undo_idna(label, r, label, sizeof(label));
273 if (k < 0)
274 return k;
275 if (k > 0)
276 r = k;
277
74b2466e
LP
278 r = dns_label_escape(label, r, &t);
279 if (r < 0)
280 return r;
281
282 if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
283 return -ENOMEM;
284
285 if (!first)
286 ret[n++] = '.';
287 else
288 first = false;
289
290 memcpy(ret + n, t, r);
291 n += r;
292 }
293
76f468c8
LP
294 if (n > DNS_NAME_MAX)
295 return -EINVAL;
296
74b2466e
LP
297 if (!GREEDY_REALLOC(ret, allocated, n + 1))
298 return -ENOMEM;
299
300 ret[n] = 0;
7b9f7afc
LP
301
302 if (_ret) {
303 *_ret = ret;
304 ret = NULL;
305 }
74b2466e
LP
306
307 return 0;
308}
309
310unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
311 const char *p = s;
322345fd 312 unsigned long ul = hash_key[0];
74b2466e
LP
313 int r;
314
315 assert(p);
316
317 while (*p) {
318 char label[DNS_LABEL_MAX+1];
bdf10b5b 319 int k;
74b2466e
LP
320
321 r = dns_label_unescape(&p, label, sizeof(label));
322 if (r < 0)
323 break;
324
bdf10b5b
LP
325 k = dns_label_undo_idna(label, r, label, sizeof(label));
326 if (k < 0)
7da40fc1 327 break;
bdf10b5b
LP
328 if (k > 0)
329 r = k;
330
74b2466e
LP
331 label[r] = 0;
332 ascii_strlower(label);
333
322345fd 334 ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
74b2466e
LP
335 }
336
337 return ul;
338}
339
340int dns_name_compare_func(const void *a, const void *b) {
341 const char *x = a, *y = b;
bdf10b5b 342 int r, q, k, w;
74b2466e
LP
343
344 assert(a);
345 assert(b);
346
347 for (;;) {
348 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
349
350 if (*x == 0 && *y == 0)
351 return 0;
352
353 r = dns_label_unescape(&x, la, sizeof(la));
354 q = dns_label_unescape(&y, lb, sizeof(lb));
355 if (r < 0 || q < 0)
356 return r - q;
357
bdf10b5b
LP
358 k = dns_label_undo_idna(la, r, la, sizeof(la));
359 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
360 if (k < 0 || w < 0)
361 return k - w;
362 if (k > 0)
363 r = k;
364 if (w > 0)
365 r = w;
366
74b2466e
LP
367 la[r] = lb[q] = 0;
368 r = strcasecmp(la, lb);
369 if (r != 0)
370 return r;
371 }
372}
373
d5099efc
MS
374const struct hash_ops dns_name_hash_ops = {
375 .hash = dns_name_hash_func,
376 .compare = dns_name_compare_func
377};
378
74b2466e 379int dns_name_equal(const char *x, const char *y) {
bdf10b5b 380 int r, q, k, w;
74b2466e
LP
381
382 assert(x);
383 assert(y);
384
385 for (;;) {
386 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
387
388 if (*x == 0 && *y == 0)
389 return true;
390
391 r = dns_label_unescape(&x, la, sizeof(la));
392 if (r < 0)
393 return r;
394
bdf10b5b
LP
395 k = dns_label_undo_idna(la, r, la, sizeof(la));
396 if (k < 0)
397 return k;
398 if (k > 0)
399 r = k;
400
74b2466e
LP
401 q = dns_label_unescape(&y, lb, sizeof(lb));
402 if (q < 0)
403 return q;
bdf10b5b
LP
404 w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
405 if (w < 0)
406 return w;
407 if (w > 0)
408 q = w;
74b2466e
LP
409
410 la[r] = lb[q] = 0;
411 if (strcasecmp(la, lb))
412 return false;
413 }
414}
415
416int dns_name_endswith(const char *name, const char *suffix) {
417 const char *n, *s, *saved_n = NULL;
bdf10b5b 418 int r, q, k, w;
74b2466e
LP
419
420 assert(name);
421 assert(suffix);
422
423 n = name;
424 s = suffix;
425
426 for (;;) {
427 char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
428
429 r = dns_label_unescape(&n, ln, sizeof(ln));
430 if (r < 0)
431 return r;
bdf10b5b
LP
432 k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
433 if (k < 0)
434 return k;
435 if (k > 0)
436 r = k;
74b2466e
LP
437
438 if (!saved_n)
439 saved_n = n;
440
441 q = dns_label_unescape(&s, ls, sizeof(ls));
be754d54
LP
442 if (q < 0)
443 return q;
7da40fc1 444 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
bdf10b5b
LP
445 if (w < 0)
446 return w;
447 if (w > 0)
448 q = w;
74b2466e
LP
449
450 if (r == 0 && q == 0)
451 return true;
452 if (r == 0 && saved_n == n)
453 return false;
454
455 ln[r] = ls[q] = 0;
456
457 if (r != q || strcasecmp(ln, ls)) {
458
459 /* Not the same, let's jump back, and try with the next label again */
460 s = suffix;
461 n = saved_n;
462 saved_n = NULL;
463 }
464 }
465}
466
467int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
468 const uint8_t *p;
469 int r;
470
471 assert(a);
472 assert(ret);
473
474 p = (const uint8_t*) a;
475
476 if (family == AF_INET)
477 r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
478 else if (family == AF_INET6)
3fe1169f
LP
479 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",
480 hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
481 hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
482 hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
483 hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
484 hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
485 hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
486 hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
487 hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
74b2466e
LP
488 else
489 return -EAFNOSUPPORT;
490 if (r < 0)
491 return -ENOMEM;
492
493 return 0;
494}
495
b914e211
LP
496int dns_name_address(const char *p, int *family, union in_addr_union *address) {
497 int r;
498
499 assert(p);
500 assert(family);
501 assert(address);
502
503 r = dns_name_endswith(p, "in-addr.arpa");
504 if (r < 0)
505 return r;
506 if (r > 0) {
507 uint8_t a[4];
508 unsigned i;
509
510 for (i = 0; i < ELEMENTSOF(a); i++) {
511 char label[DNS_LABEL_MAX+1];
512
513 r = dns_label_unescape(&p, label, sizeof(label));
514 if (r < 0)
515 return r;
516 if (r == 0)
517 return -EINVAL;
518 if (r > 3)
519 return -EINVAL;
520
521 r = safe_atou8(label, &a[i]);
522 if (r < 0)
523 return r;
524 }
525
526 r = dns_name_equal(p, "in-addr.arpa");
527 if (r <= 0)
528 return r;
529
530 *family = AF_INET;
531 address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
532 ((uint32_t) a[2] << 16) |
533 ((uint32_t) a[1] << 8) |
534 (uint32_t) a[0]);
535
536 return 1;
537 }
538
539 r = dns_name_endswith(p, "ip6.arpa");
540 if (r < 0)
541 return r;
542 if (r > 0) {
543 struct in6_addr a;
544 unsigned i;
545
546 for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
547 char label[DNS_LABEL_MAX+1];
548 int x, y;
549
550 r = dns_label_unescape(&p, label, sizeof(label));
551 if (r <= 0)
552 return r;
553 if (r != 1)
554 return -EINVAL;
555 x = unhexchar(label[0]);
556 if (x < 0)
557 return -EINVAL;
558
559 r = dns_label_unescape(&p, label, sizeof(label));
560 if (r <= 0)
561 return r;
562 if (r != 1)
563 return -EINVAL;
564 y = unhexchar(label[0]);
565 if (y < 0)
566 return -EINVAL;
567
568 a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
569 }
570
571 r = dns_name_equal(p, "ip6.arpa");
572 if (r <= 0)
573 return r;
574
575 *family = AF_INET6;
576 address->in6 = a;
577 return 1;
578 }
579
580 return 0;
581}
582
74b2466e
LP
583int dns_name_root(const char *name) {
584 char label[DNS_LABEL_MAX+1];
585 int r;
586
587 assert(name);
588
589 r = dns_label_unescape(&name, label, sizeof(label));
590 if (r < 0)
591 return r;
592
593 return r == 0 && *name == 0;
594}
595
596int dns_name_single_label(const char *name) {
597 char label[DNS_LABEL_MAX+1];
598 int r;
599
600 assert(name);
601
602 r = dns_label_unescape(&name, label, sizeof(label));
603 if (r < 0)
604 return r;
74b2466e
LP
605 if (r == 0)
606 return 0;
607
608 r = dns_label_unescape(&name, label, sizeof(label));
609 if (r < 0)
610 return r;
611
612 return r == 0 && *name == 0;
613}