chiark / gitweb /
resolve: move dns routines into shared
[elogind.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
29 int 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
117 int 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
125         if (l > DNS_LABEL_MAX)
126                 return -EINVAL;
127
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;
149                 } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
150
151                         /* Everything else */
152                         *(q++) = '\\';
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);
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
172 int 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
206 int 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
249 int 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);
257
258         for (;;) {
259                 _cleanup_free_ char *t = NULL;
260                 char label[DNS_LABEL_MAX];
261                 int k;
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
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
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
294         if (n > DNS_NAME_MAX)
295                 return -EINVAL;
296
297         if (!GREEDY_REALLOC(ret, allocated, n + 1))
298                 return -ENOMEM;
299
300         ret[n] = 0;
301
302         if (_ret) {
303                 *_ret = ret;
304                 ret = NULL;
305         }
306
307         return 0;
308 }
309
310 unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
311         const char *p = s;
312         unsigned long ul = hash_key[0];
313         int r;
314
315         assert(p);
316
317         while (*p) {
318                 char label[DNS_LABEL_MAX+1];
319                 int k;
320
321                 r = dns_label_unescape(&p, label, sizeof(label));
322                 if (r < 0)
323                         break;
324
325                 k = dns_label_undo_idna(label, r, label, sizeof(label));
326                 if (k < 0)
327                         break;
328                 if (k > 0)
329                         r = k;
330
331                 label[r] = 0;
332                 ascii_strlower(label);
333
334                 ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
335         }
336
337         return ul;
338 }
339
340 int dns_name_compare_func(const void *a, const void *b) {
341         const char *x = a, *y = b;
342         int r, q, k, w;
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
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
367                 la[r] = lb[q] = 0;
368                 r = strcasecmp(la, lb);
369                 if (r != 0)
370                         return r;
371         }
372 }
373
374 const struct hash_ops dns_name_hash_ops = {
375         .hash = dns_name_hash_func,
376         .compare = dns_name_compare_func
377 };
378
379 int dns_name_equal(const char *x, const char *y) {
380         int r, q, k, w;
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
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
401                 q = dns_label_unescape(&y, lb, sizeof(lb));
402                 if (q < 0)
403                         return q;
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;
409
410                 la[r] = lb[q] = 0;
411                 if (strcasecmp(la, lb))
412                         return false;
413         }
414 }
415
416 int dns_name_endswith(const char *name, const char *suffix) {
417         const char *n, *s, *saved_n = NULL;
418         int r, q, k, w;
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;
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;
437
438                 if (!saved_n)
439                         saved_n = n;
440
441                 q = dns_label_unescape(&s, ls, sizeof(ls));
442                 if (q < 0)
443                         return q;
444                 w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
445                 if (w < 0)
446                         return w;
447                 if (w > 0)
448                         q = w;
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
467 int 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)
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));
488         else
489                 return -EAFNOSUPPORT;
490         if (r < 0)
491                 return -ENOMEM;
492
493         return 0;
494 }
495
496 int 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
583 int 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
596 int 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;
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 }