chiark / gitweb /
resolved: various fixes regarding encoding of UTF8 characters in DNS RRs
[elogind.git] / src / resolve / resolved-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 #include "resolved-dns-domain.h"
23
24 int dns_label_unescape(const char **name, char *dest, size_t sz) {
25         const char *n;
26         char *d;
27         int r = 0;
28
29         assert(name);
30         assert(*name);
31         assert(dest);
32
33         n = *name;
34         d = dest;
35
36         for (;;) {
37                 if (*n == '.') {
38                         n++;
39                         break;
40                 }
41
42                 if (*n == 0)
43                         break;
44
45                 if (sz <= 0)
46                         return -ENOSPC;
47
48                 if (r >= DNS_LABEL_MAX)
49                         return -EINVAL;
50
51                 if (*n == '\\') {
52                         /* Escaped character */
53
54                         n++;
55
56                         if (*n == 0)
57                                 /* Ending NUL */
58                                 return -EINVAL;
59
60                         else if (*n == '\\' || *n == '.') {
61                                 /* Escaped backslash or dot */
62                                 *(d++) = *(n++);
63                                 sz--;
64                                 r++;
65
66                         } else if (n[0] >= '0' && n[0] <= '9') {
67                                 unsigned k;
68
69                                 /* Escaped literal ASCII character */
70
71                                 if (!(n[1] >= '0' && n[1] <= '9') ||
72                                     !(n[2] >= '0' && n[2] <= '9'))
73                                         return -EINVAL;
74
75                                 k = ((unsigned) (n[0] - '0') * 100) +
76                                         ((unsigned) (n[1] - '0') * 10) +
77                                         ((unsigned) (n[2] - '0'));
78
79                                 /* Don't allow CC characters or anything that doesn't fit in 8bit */
80                                 if (k < ' ' || k > 255 || k == 127)
81                                         return -EINVAL;
82
83                                 *(d++) = (char) k;
84                                 sz--;
85                                 r++;
86
87                                 n += 3;
88                         } else
89                                 return -EINVAL;
90
91                 } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
92
93                         /* Normal character */
94                         *(d++) = *(n++);
95                         sz--;
96                         r++;
97                 } else
98                         return -EINVAL;
99         }
100
101         /* Empty label that is not at the end? */
102         if (r == 0 && *n)
103                 return -EINVAL;
104
105         if (sz >= 1)
106                 *d = 0;
107
108         *name = n;
109         return r;
110 }
111
112 int dns_label_escape(const char *p, size_t l, char **ret) {
113         _cleanup_free_ char *s = NULL;
114         char *q;
115         int r;
116
117         assert(p);
118         assert(ret);
119
120         if (l > DNS_LABEL_MAX)
121                 return -EINVAL;
122
123         s = malloc(l * 4 + 1);
124         if (!s)
125                 return -ENOMEM;
126
127         q = s;
128         while (l > 0) {
129
130                 if (*p == '.' || *p == '\\') {
131
132                         /* Dot or backslash */
133                         *(q++) = '\\';
134                         *(q++) = *p;
135
136                 } else if (*p == '_' ||
137                            *p == '-' ||
138                            (*p >= '0' && *p <= '9') ||
139                            (*p >= 'a' && *p <= 'z') ||
140                            (*p >= 'A' && *p <= 'Z')) {
141
142                         /* Proper character */
143                         *(q++) = *p;
144                 } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
145
146                         /* Everything else */
147                         *(q++) = '\\';
148                         *(q++) = '0' + (char) ((uint8_t) *p / 100);
149                         *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
150                         *(q++) = '0' + (char) ((uint8_t) *p % 10);
151
152                 } else
153                         return -EINVAL;
154
155                 p++;
156                 l--;
157         }
158
159         *q = 0;
160         *ret = s;
161         r = q - s;
162         s = NULL;
163
164         return r;
165 }
166
167 int dns_name_normalize(const char *s, char **_ret) {
168         _cleanup_free_ char *ret = NULL;
169         size_t n = 0, allocated = 0;
170         const char *p = s;
171         bool first = true;
172         int r;
173
174         assert(s);
175         assert(_ret);
176
177         for (;;) {
178                 _cleanup_free_ char *t = NULL;
179                 char label[DNS_LABEL_MAX];
180
181                 r = dns_label_unescape(&p, label, sizeof(label));
182                 if (r < 0)
183                         return r;
184                 if (r == 0) {
185                         if (*p != 0)
186                                 return -EINVAL;
187                         break;
188                 }
189
190                 r = dns_label_escape(label, r, &t);
191                 if (r < 0)
192                         return r;
193
194                 if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1))
195                         return -ENOMEM;
196
197                 if (!first)
198                         ret[n++] = '.';
199                 else
200                         first = false;
201
202                 memcpy(ret + n, t, r);
203                 n += r;
204         }
205
206         if (n > DNS_NAME_MAX)
207                 return -EINVAL;
208
209         if (!GREEDY_REALLOC(ret, allocated, n + 1))
210                 return -ENOMEM;
211
212         ret[n] = 0;
213         *_ret = ret;
214         ret = NULL;
215
216         return 0;
217 }
218
219 unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
220         const char *p = s;
221         unsigned long ul = hash_key[0];
222         int r;
223
224         assert(p);
225
226         while (*p) {
227                 char label[DNS_LABEL_MAX+1];
228
229                 r = dns_label_unescape(&p, label, sizeof(label));
230                 if (r < 0)
231                         break;
232
233                 label[r] = 0;
234                 ascii_strlower(label);
235
236                 ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key);
237         }
238
239         return ul;
240 }
241
242 int dns_name_compare_func(const void *a, const void *b) {
243         const char *x = a, *y = b;
244         int r, q;
245
246         assert(a);
247         assert(b);
248
249         for (;;) {
250                 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
251
252                 if (*x == 0 && *y == 0)
253                         return 0;
254
255                 r = dns_label_unescape(&x, la, sizeof(la));
256                 q = dns_label_unescape(&y, lb, sizeof(lb));
257                 if (r < 0 || q < 0)
258                         return r - q;
259
260                 la[r] = lb[q] = 0;
261                 r = strcasecmp(la, lb);
262                 if (r != 0)
263                         return r;
264         }
265 }
266
267 int dns_name_equal(const char *x, const char *y) {
268         int r, q;
269
270         assert(x);
271         assert(y);
272
273         for (;;) {
274                 char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
275
276                 if (*x == 0 && *y == 0)
277                         return true;
278
279                 r = dns_label_unescape(&x, la, sizeof(la));
280                 if (r < 0)
281                         return r;
282
283                 q = dns_label_unescape(&y, lb, sizeof(lb));
284                 if (q < 0)
285                         return q;
286
287                 la[r] = lb[q] = 0;
288                 if (strcasecmp(la, lb))
289                         return false;
290         }
291 }
292
293 int dns_name_endswith(const char *name, const char *suffix) {
294         const char *n, *s, *saved_n = NULL;
295         int r, q;
296
297         assert(name);
298         assert(suffix);
299
300         n = name;
301         s = suffix;
302
303         for (;;) {
304                 char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
305
306                 r = dns_label_unescape(&n, ln, sizeof(ln));
307                 if (r < 0)
308                         return r;
309
310                 if (!saved_n)
311                         saved_n = n;
312
313                 q = dns_label_unescape(&s, ls, sizeof(ls));
314                 if (r < 0)
315                         return r;
316
317                 if (r == 0 && q == 0)
318                         return true;
319                 if (r == 0 && saved_n == n)
320                         return false;
321
322                 ln[r] = ls[q] = 0;
323
324                 if (r != q || strcasecmp(ln, ls)) {
325
326                         /* Not the same, let's jump back, and try with the next label again */
327                         s = suffix;
328                         n = saved_n;
329                         saved_n = NULL;
330                 }
331         }
332 }
333
334 int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
335         const uint8_t *p;
336         int r;
337
338         assert(a);
339         assert(ret);
340
341         p = (const uint8_t*) a;
342
343         if (family == AF_INET)
344                 r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
345         else if (family == AF_INET6)
346                 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",
347                              hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
348                              hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
349                              hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
350                              hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
351                              hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
352                              hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
353                              hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
354                              hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
355         else
356                 return -EAFNOSUPPORT;
357         if (r < 0)
358                 return -ENOMEM;
359
360         return 0;
361 }
362
363 int dns_name_address(const char *p, int *family, union in_addr_union *address) {
364         int r;
365
366         assert(p);
367         assert(family);
368         assert(address);
369
370         r = dns_name_endswith(p, "in-addr.arpa");
371         if (r < 0)
372                 return r;
373         if (r > 0) {
374                 uint8_t a[4];
375                 unsigned i;
376
377                 for (i = 0; i < ELEMENTSOF(a); i++) {
378                         char label[DNS_LABEL_MAX+1];
379
380                         r = dns_label_unescape(&p, label, sizeof(label));
381                         if (r < 0)
382                                 return r;
383                         if (r == 0)
384                                 return -EINVAL;
385                         if (r > 3)
386                                 return -EINVAL;
387
388                         r = safe_atou8(label, &a[i]);
389                         if (r < 0)
390                                 return r;
391                 }
392
393                 r = dns_name_equal(p, "in-addr.arpa");
394                 if (r <= 0)
395                         return r;
396
397                 *family = AF_INET;
398                 address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
399                                              ((uint32_t) a[2] << 16) |
400                                              ((uint32_t) a[1] << 8) |
401                                               (uint32_t) a[0]);
402
403                 return 1;
404         }
405
406         r = dns_name_endswith(p, "ip6.arpa");
407         if (r < 0)
408                 return r;
409         if (r > 0) {
410                 struct in6_addr a;
411                 unsigned i;
412
413                 for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
414                         char label[DNS_LABEL_MAX+1];
415                         int x, y;
416
417                         r = dns_label_unescape(&p, label, sizeof(label));
418                         if (r <= 0)
419                                 return r;
420                         if (r != 1)
421                                 return -EINVAL;
422                         x = unhexchar(label[0]);
423                         if (x < 0)
424                                 return -EINVAL;
425
426                         r = dns_label_unescape(&p, label, sizeof(label));
427                         if (r <= 0)
428                                 return r;
429                         if (r != 1)
430                                 return -EINVAL;
431                         y = unhexchar(label[0]);
432                         if (y < 0)
433                                 return -EINVAL;
434
435                         a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
436                 }
437
438                 r = dns_name_equal(p, "ip6.arpa");
439                 if (r <= 0)
440                         return r;
441
442                 *family = AF_INET6;
443                 address->in6 = a;
444                 return 1;
445         }
446
447         return 0;
448 }
449
450 int dns_name_root(const char *name) {
451         char label[DNS_LABEL_MAX+1];
452         int r;
453
454         assert(name);
455
456         r = dns_label_unescape(&name, label, sizeof(label));
457         if (r < 0)
458                 return r;
459
460         return r == 0 && *name == 0;
461 }
462
463 int dns_name_single_label(const char *name) {
464         char label[DNS_LABEL_MAX+1];
465         int r;
466
467         assert(name);
468
469         r = dns_label_unescape(&name, label, sizeof(label));
470         if (r < 0)
471                 return r;
472         if (r == 0)
473                 return 0;
474
475         r = dns_label_unescape(&name, label, sizeof(label));
476         if (r < 0)
477                 return r;
478
479         return r == 0 && *name == 0;
480 }