/src/systemd/src/shared/dns-domain.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <endian.h> |
4 | | #include <netinet/in.h> |
5 | | #include <stdio.h> |
6 | | #include <sys/socket.h> |
7 | | |
8 | | #include "alloc-util.h" |
9 | | #include "dns-def.h" |
10 | | #include "dns-domain.h" |
11 | | #include "glyph-util.h" |
12 | | #include "hash-funcs.h" |
13 | | #include "hexdecoct.h" |
14 | | #include "hostname-util.h" |
15 | | #include "idn-util.h" |
16 | | #include "in-addr-util.h" |
17 | | #include "log.h" |
18 | | #include "parse-util.h" |
19 | | #include "siphash24.h" |
20 | | #include "string-util.h" |
21 | | #include "strv.h" |
22 | | #include "utf8.h" |
23 | | |
24 | 20.6M | int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) { |
25 | 20.6M | const char *n; |
26 | 20.6M | char *d, last_char = 0; |
27 | 20.6M | int r = 0; |
28 | | |
29 | 20.6M | assert(name); |
30 | 20.6M | assert(*name); |
31 | | |
32 | 20.6M | n = *name; |
33 | 20.6M | d = dest; |
34 | | |
35 | 90.0M | for (;;) { |
36 | 90.0M | if (IN_SET(*n, 0, '.')) { |
37 | 20.5M | if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-') |
38 | | /* Trailing dash */ |
39 | 766 | return -EINVAL; |
40 | | |
41 | 20.5M | if (n[0] == '.' && (n[1] != 0 || !FLAGS_SET(flags, DNS_LABEL_LEAVE_TRAILING_DOT))) |
42 | 7.18M | n++; |
43 | | |
44 | 20.5M | break; |
45 | 20.5M | } |
46 | | |
47 | 69.4M | if (r >= DNS_LABEL_MAX) |
48 | 3.27k | return -EINVAL; |
49 | | |
50 | 69.4M | if (sz <= 0) |
51 | 0 | return -ENOBUFS; |
52 | | |
53 | 69.4M | if (*n == '\\') { |
54 | | /* Escaped character */ |
55 | 2.12M | if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES)) |
56 | 1.03k | return -EINVAL; |
57 | | |
58 | 2.12M | n++; |
59 | | |
60 | 2.12M | if (*n == 0) |
61 | | /* Ending NUL */ |
62 | 556 | return -EINVAL; |
63 | | |
64 | 2.12M | else if (IN_SET(*n, '\\', '.')) { |
65 | | /* Escaped backslash or dot */ |
66 | | |
67 | 153k | if (FLAGS_SET(flags, DNS_LABEL_LDH)) |
68 | 0 | return -EINVAL; |
69 | | |
70 | 153k | last_char = *n; |
71 | 153k | if (d) |
72 | 153k | *(d++) = *n; |
73 | 153k | sz--; |
74 | 153k | r++; |
75 | 153k | n++; |
76 | | |
77 | 1.96M | } else if (n[0] >= '0' && n[0] <= '9') { |
78 | 1.96M | unsigned k; |
79 | | |
80 | | /* Escaped literal ASCII character */ |
81 | | |
82 | 1.96M | if (!(n[1] >= '0' && n[1] <= '9') || |
83 | 1.96M | !(n[2] >= '0' && n[2] <= '9')) |
84 | 2.20k | return -EINVAL; |
85 | | |
86 | 1.96M | k = ((unsigned) (n[0] - '0') * 100) + |
87 | 1.96M | ((unsigned) (n[1] - '0') * 10) + |
88 | 1.96M | ((unsigned) (n[2] - '0')); |
89 | | |
90 | | /* Don't allow anything that doesn't fit in 8 bits. Note that we do allow |
91 | | * control characters, as some servers (e.g. cloudflare) are happy to |
92 | | * generate labels with them inside. */ |
93 | 1.96M | if (k > 255) |
94 | 404 | return -EINVAL; |
95 | | |
96 | 1.96M | if (FLAGS_SET(flags, DNS_LABEL_LDH) && |
97 | 1.96M | !valid_ldh_char((char) k)) |
98 | 0 | return -EINVAL; |
99 | | |
100 | 1.96M | last_char = (char) k; |
101 | 1.96M | if (d) |
102 | 1.96M | *(d++) = (char) k; |
103 | 1.96M | sz--; |
104 | 1.96M | r++; |
105 | | |
106 | 1.96M | n += 3; |
107 | 1.96M | } else |
108 | 1.21k | return -EINVAL; |
109 | | |
110 | 67.3M | } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { |
111 | | |
112 | | /* Normal character */ |
113 | | |
114 | 67.3M | if (FLAGS_SET(flags, DNS_LABEL_LDH)) { |
115 | 15.7M | if (!valid_ldh_char(*n)) |
116 | 32.2k | return -EINVAL; |
117 | 15.6M | if (r == 0 && *n == '-') |
118 | | /* Leading dash */ |
119 | 1.38k | return -EINVAL; |
120 | 15.6M | } |
121 | | |
122 | 67.3M | last_char = *n; |
123 | 67.3M | if (d) |
124 | 67.2M | *(d++) = *n; |
125 | 67.3M | sz--; |
126 | 67.3M | r++; |
127 | 67.3M | n++; |
128 | 67.3M | } else |
129 | 24.7k | return -EINVAL; |
130 | 69.4M | } |
131 | | |
132 | | /* Empty label that is not at the end? */ |
133 | 20.5M | if (r == 0 && *n) |
134 | 7.57k | return -EINVAL; |
135 | | |
136 | | /* More than one trailing dot? */ |
137 | 20.5M | if (n[0] == '.' && !FLAGS_SET(flags, DNS_LABEL_LEAVE_TRAILING_DOT)) |
138 | 2.88k | return -EINVAL; |
139 | | |
140 | 20.5M | if (sz >= 1 && d) |
141 | 20.5M | *d = 0; |
142 | | |
143 | 20.5M | *name = n; |
144 | 20.5M | return r; |
145 | 20.5M | } |
146 | | |
147 | | /* @label_terminal: terminal character of a label, updated to point to the terminal character of |
148 | | * the previous label (always skipping one dot) or to NULL if there are no more |
149 | | * labels. */ |
150 | 4.94M | int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { |
151 | 4.94M | const char *terminal; |
152 | 4.94M | int r; |
153 | | |
154 | 4.94M | assert(name); |
155 | 4.94M | assert(label_terminal); |
156 | 4.94M | assert(dest); |
157 | | |
158 | | /* no more labels */ |
159 | 4.94M | if (!*label_terminal) { |
160 | 6.66k | if (sz >= 1) |
161 | 6.66k | *dest = 0; |
162 | | |
163 | 6.66k | return 0; |
164 | 6.66k | } |
165 | | |
166 | 4.93M | terminal = *label_terminal; |
167 | 4.93M | assert(IN_SET(*terminal, 0, '.')); |
168 | | |
169 | | /* Skip current terminal character (and accept domain names ending it ".") */ |
170 | 4.93M | if (*terminal == 0) |
171 | 4.34M | terminal = PTR_SUB1(terminal, name); |
172 | 4.93M | if (terminal >= name && *terminal == '.') |
173 | 1.60M | terminal = PTR_SUB1(terminal, name); |
174 | | |
175 | | /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ |
176 | 20.0M | while (terminal) { |
177 | | /* Find the start of the last label */ |
178 | 16.4M | if (*terminal == '.') { |
179 | 1.40M | const char *y; |
180 | 1.40M | unsigned slashes = 0; |
181 | | |
182 | 1.42M | for (y = PTR_SUB1(terminal, name); y && *y == '\\'; y = PTR_SUB1(y, name)) |
183 | 18.1k | slashes++; |
184 | | |
185 | 1.40M | if (slashes % 2 == 0) { |
186 | | /* The '.' was not escaped */ |
187 | 1.39M | name = terminal + 1; |
188 | 1.39M | break; |
189 | 1.39M | } else { |
190 | 17.9k | terminal = y; |
191 | 17.9k | continue; |
192 | 17.9k | } |
193 | 1.40M | } |
194 | | |
195 | 15.0M | terminal = PTR_SUB1(terminal, name); |
196 | 15.0M | } |
197 | | |
198 | 4.93M | r = dns_label_unescape(&name, dest, sz, 0); |
199 | 4.93M | if (r < 0) |
200 | 0 | return r; |
201 | | |
202 | 4.93M | *label_terminal = terminal; |
203 | | |
204 | 4.93M | return r; |
205 | 4.93M | } |
206 | | |
207 | 2.96M | int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { |
208 | 2.96M | char *q; |
209 | | |
210 | | /* DNS labels must be between 1 and 63 characters long. A |
211 | | * zero-length label does not exist. See RFC 2181, Section |
212 | | * 11. */ |
213 | | |
214 | 2.96M | if (l <= 0 || l > DNS_LABEL_MAX) |
215 | 0 | return -EINVAL; |
216 | 2.96M | if (sz < 1) |
217 | 0 | return -ENOBUFS; |
218 | | |
219 | 2.96M | assert(p); |
220 | 2.96M | assert(dest); |
221 | | |
222 | 2.96M | q = dest; |
223 | 20.8M | while (l > 0) { |
224 | | |
225 | 17.8M | if (IN_SET(*p, '.', '\\')) { |
226 | | |
227 | | /* Dot or backslash */ |
228 | | |
229 | 77.2k | if (sz < 3) |
230 | 0 | return -ENOBUFS; |
231 | | |
232 | 77.2k | *(q++) = '\\'; |
233 | 77.2k | *(q++) = *p; |
234 | | |
235 | 77.2k | sz -= 2; |
236 | | |
237 | 17.7M | } else if (IN_SET(*p, '_', '-') || |
238 | 17.7M | ascii_isdigit(*p) || |
239 | 17.7M | ascii_isalpha(*p)) { |
240 | | |
241 | | /* Proper character */ |
242 | | |
243 | 16.0M | if (sz < 2) |
244 | 0 | return -ENOBUFS; |
245 | | |
246 | 16.0M | *(q++) = *p; |
247 | 16.0M | sz -= 1; |
248 | | |
249 | 16.0M | } else { |
250 | | |
251 | | /* Everything else */ |
252 | | |
253 | 1.76M | if (sz < 5) |
254 | 0 | return -ENOBUFS; |
255 | | |
256 | 1.76M | *(q++) = '\\'; |
257 | 1.76M | *(q++) = '0' + (char) ((uint8_t) *p / 100); |
258 | 1.76M | *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); |
259 | 1.76M | *(q++) = '0' + (char) ((uint8_t) *p % 10); |
260 | | |
261 | 1.76M | sz -= 4; |
262 | 1.76M | } |
263 | | |
264 | 17.8M | p++; |
265 | 17.8M | l--; |
266 | 17.8M | } |
267 | | |
268 | 2.96M | *q = 0; |
269 | 2.96M | return (int) (q - dest); |
270 | 2.96M | } |
271 | | |
272 | 0 | int dns_label_escape_new(const char *p, size_t l, char **ret) { |
273 | 0 | _cleanup_free_ char *s = NULL; |
274 | 0 | int r; |
275 | |
|
276 | 0 | assert(p); |
277 | 0 | assert(ret); |
278 | |
|
279 | 0 | if (l <= 0 || l > DNS_LABEL_MAX) |
280 | 0 | return -EINVAL; |
281 | | |
282 | 0 | s = new(char, DNS_LABEL_ESCAPED_MAX); |
283 | 0 | if (!s) |
284 | 0 | return -ENOMEM; |
285 | | |
286 | 0 | r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); |
287 | 0 | if (r < 0) |
288 | 0 | return r; |
289 | | |
290 | 0 | *ret = TAKE_PTR(s); |
291 | |
|
292 | 0 | return r; |
293 | 0 | } |
294 | | |
295 | 4.21k | int dns_name_parent(const char **name) { |
296 | 4.21k | return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0); |
297 | 4.21k | } |
298 | | |
299 | | #if HAVE_LIBIDN |
300 | | int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { |
301 | | _cleanup_free_ uint32_t *input = NULL; |
302 | | size_t input_size, l; |
303 | | bool contains_8_bit = false; |
304 | | char buffer[DNS_LABEL_MAX+1]; |
305 | | int r; |
306 | | |
307 | | assert(encoded); |
308 | | assert(decoded); |
309 | | |
310 | | /* Converts a U-label into an A-label */ |
311 | | |
312 | | r = dlopen_idn(); |
313 | | if (r < 0) |
314 | | return r; |
315 | | |
316 | | if (encoded_size <= 0) |
317 | | return -EINVAL; |
318 | | |
319 | | for (const char *p = encoded; p < encoded + encoded_size; p++) |
320 | | if ((uint8_t) *p > 127) |
321 | | contains_8_bit = true; |
322 | | |
323 | | if (!contains_8_bit) { |
324 | | if (encoded_size > DNS_LABEL_MAX) |
325 | | return -EINVAL; |
326 | | |
327 | | return 0; |
328 | | } |
329 | | |
330 | | input = sym_stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); |
331 | | if (!input) |
332 | | return -ENOMEM; |
333 | | |
334 | | if (sym_idna_to_ascii_4i(input, input_size, buffer, 0) != 0) |
335 | | return -EINVAL; |
336 | | |
337 | | l = strlen(buffer); |
338 | | |
339 | | /* Verify that the result is not longer than one DNS label. */ |
340 | | if (l <= 0 || l > DNS_LABEL_MAX) |
341 | | return -EINVAL; |
342 | | if (l > decoded_max) |
343 | | return -ENOBUFS; |
344 | | |
345 | | memcpy(decoded, buffer, l); |
346 | | |
347 | | /* If there's room, append a trailing NUL byte, but only then */ |
348 | | if (decoded_max > l) |
349 | | decoded[l] = 0; |
350 | | |
351 | | return (int) l; |
352 | | } |
353 | | |
354 | | int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { |
355 | | size_t input_size, output_size; |
356 | | _cleanup_free_ uint32_t *input = NULL; |
357 | | _cleanup_free_ char *result = NULL; |
358 | | uint32_t *output = NULL; |
359 | | size_t w; |
360 | | int r; |
361 | | |
362 | | /* To be invoked after unescaping. Converts an A-label into a U-label. */ |
363 | | |
364 | | assert(encoded); |
365 | | assert(decoded); |
366 | | |
367 | | r = dlopen_idn(); |
368 | | if (r < 0) |
369 | | return r; |
370 | | |
371 | | if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) |
372 | | return -EINVAL; |
373 | | |
374 | | if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX)) |
375 | | return 0; |
376 | | |
377 | | input = sym_stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); |
378 | | if (!input) |
379 | | return -ENOMEM; |
380 | | |
381 | | output_size = input_size; |
382 | | output = newa(uint32_t, output_size); |
383 | | |
384 | | sym_idna_to_unicode_44i(input, input_size, output, &output_size, 0); |
385 | | |
386 | | result = sym_stringprep_ucs4_to_utf8(output, output_size, NULL, &w); |
387 | | if (!result) |
388 | | return -ENOMEM; |
389 | | if (w <= 0) |
390 | | return -EINVAL; |
391 | | if (w > decoded_max) |
392 | | return -ENOBUFS; |
393 | | |
394 | | memcpy(decoded, result, w); |
395 | | |
396 | | /* Append trailing NUL byte if there's space, but only then. */ |
397 | | if (decoded_max > w) |
398 | | decoded[w] = 0; |
399 | | |
400 | | return w; |
401 | | } |
402 | | #endif |
403 | | |
404 | 1.62M | int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret) { |
405 | 1.62M | _cleanup_free_ char *result = NULL; |
406 | 1.62M | size_t n_result = 0, n_unescaped = 0; |
407 | 1.62M | const char *p; |
408 | 1.62M | bool first = true; |
409 | 1.62M | int r; |
410 | | |
411 | 1.62M | if (a) |
412 | 1.62M | p = a; |
413 | 0 | else if (b) |
414 | 0 | p = TAKE_PTR(b); |
415 | 0 | else |
416 | 0 | goto finish; |
417 | | |
418 | 4.34M | for (;;) { |
419 | 4.34M | char label[DNS_LABEL_MAX+1]; |
420 | | |
421 | 4.34M | r = dns_label_unescape(&p, label, sizeof label, flags); |
422 | 4.34M | if (r < 0) |
423 | 78.2k | return r; |
424 | 4.26M | if (r == 0) { |
425 | 1.54M | if (*p != 0) |
426 | 0 | return -EINVAL; |
427 | | |
428 | 1.54M | if (b) { |
429 | | /* Now continue with the second string, if there is one */ |
430 | 0 | p = TAKE_PTR(b); |
431 | 0 | continue; |
432 | 0 | } |
433 | | |
434 | 1.54M | break; |
435 | 1.54M | } |
436 | 2.71M | n_unescaped += r + !first; /* Count unescaped length to make max length determination below */ |
437 | | |
438 | 2.71M | if (ret) { |
439 | 46.2k | if (!GREEDY_REALLOC(result, n_result + !first + DNS_LABEL_ESCAPED_MAX)) |
440 | 0 | return -ENOMEM; |
441 | | |
442 | 46.2k | r = dns_label_escape(label, r, result + n_result + !first, DNS_LABEL_ESCAPED_MAX); |
443 | 46.2k | if (r < 0) |
444 | 0 | return r; |
445 | | |
446 | 46.2k | if (!first) |
447 | 19.8k | result[n_result] = '.'; |
448 | 2.67M | } else { |
449 | 2.67M | char escaped[DNS_LABEL_ESCAPED_MAX]; |
450 | | |
451 | 2.67M | r = dns_label_escape(label, r, escaped, sizeof(escaped)); |
452 | 2.67M | if (r < 0) |
453 | 0 | return r; |
454 | 2.67M | } |
455 | | |
456 | 2.71M | n_result += r + !first; |
457 | 2.71M | first = false; |
458 | 2.71M | } |
459 | | |
460 | 1.54M | finish: |
461 | 1.54M | if (n_unescaped == 0) { |
462 | | /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ |
463 | | |
464 | 143k | if (ret) { |
465 | 991 | if (!GREEDY_REALLOC(result, 2)) /* Room for dot, and already pre-allocate space for the trailing NUL byte at the same time */ |
466 | 0 | return -ENOMEM; |
467 | | |
468 | 991 | result[n_result++] = '.'; |
469 | 991 | } |
470 | | |
471 | 143k | n_unescaped++; |
472 | 143k | } |
473 | | |
474 | 1.54M | if (n_unescaped > DNS_HOSTNAME_MAX) /* Enforce max length check on unescaped length */ |
475 | 901 | return -EINVAL; |
476 | | |
477 | 1.54M | if (ret) { |
478 | | /* Suffix with a NUL byte */ |
479 | 26.9k | if (!GREEDY_REALLOC(result, n_result + 1)) |
480 | 0 | return -ENOMEM; |
481 | | |
482 | 26.9k | result[n_result] = 0; |
483 | 26.9k | *ret = TAKE_PTR(result); |
484 | 26.9k | } |
485 | | |
486 | 1.54M | return 0; |
487 | 1.54M | } |
488 | | |
489 | 3.89M | void dns_name_hash_func(const char *name, struct siphash *state) { |
490 | 3.89M | int r; |
491 | | |
492 | 3.89M | assert(name); |
493 | | |
494 | 10.8M | for (const char *p = name;;) { |
495 | 10.8M | char label[DNS_LABEL_MAX+1]; |
496 | | |
497 | 10.8M | r = dns_label_unescape(&p, label, sizeof label, 0); |
498 | 10.8M | if (r < 0) |
499 | 0 | return string_hash_func(p, state); /* fallback for invalid DNS names */ |
500 | 10.8M | if (r == 0) |
501 | 3.89M | break; |
502 | | |
503 | 6.94M | ascii_strlower_n(label, r); |
504 | 6.94M | siphash24_compress(label, r, state); |
505 | 6.94M | siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ |
506 | 6.94M | } |
507 | | |
508 | | /* enforce that all names are terminated by the empty label */ |
509 | 3.89M | string_hash_func("", state); |
510 | 3.89M | } |
511 | | |
512 | 2.17M | int dns_name_compare_func(const char *a, const char *b) { |
513 | 2.17M | const char *x, *y; |
514 | 2.17M | int r, q; |
515 | | |
516 | 2.17M | assert(a); |
517 | 2.17M | assert(b); |
518 | | |
519 | 2.17M | x = a + strlen(a); |
520 | 2.17M | y = b + strlen(b); |
521 | | |
522 | 3.70M | for (;;) { |
523 | 3.70M | char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; |
524 | | |
525 | 3.70M | if (!x && !y) |
526 | 1.23M | return 0; |
527 | | |
528 | 2.47M | r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); |
529 | 2.47M | q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); |
530 | 2.47M | if (r < 0 || q < 0) |
531 | 0 | return strcmp(a, b); /* if not valid DNS labels, then let's compare the whole strings as is */ |
532 | | |
533 | 2.47M | r = ascii_strcasecmp_nn(la, r, lb, q); |
534 | 2.47M | if (r != 0) |
535 | 936k | return r; |
536 | 2.47M | } |
537 | 2.17M | } |
538 | | |
539 | | DEFINE_HASH_OPS( |
540 | | dns_name_hash_ops, |
541 | | char, |
542 | | dns_name_hash_func, |
543 | | dns_name_compare_func); |
544 | | |
545 | | DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
546 | | dns_name_hash_ops_free, |
547 | | char, |
548 | | dns_name_hash_func, |
549 | | dns_name_compare_func, |
550 | | free); |
551 | | |
552 | 202k | int dns_name_equal(const char *x, const char *y) { |
553 | 202k | int r, q; |
554 | | |
555 | 202k | assert(x); |
556 | 202k | assert(y); |
557 | | |
558 | 231k | for (;;) { |
559 | 231k | char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; |
560 | | |
561 | 231k | r = dns_label_unescape(&x, la, sizeof la, 0); |
562 | 231k | if (r < 0) |
563 | 0 | return r; |
564 | | |
565 | 231k | q = dns_label_unescape(&y, lb, sizeof lb, 0); |
566 | 231k | if (q < 0) |
567 | 0 | return q; |
568 | | |
569 | 231k | if (r != q) |
570 | 11.8k | return false; |
571 | 219k | if (r == 0) |
572 | 189k | return true; |
573 | | |
574 | 29.5k | if (ascii_strcasecmp_n(la, lb, r) != 0) |
575 | 1.21k | return false; |
576 | 29.5k | } |
577 | 202k | } |
578 | | |
579 | 0 | int dns_name_endswith(const char *name, const char *suffix) { |
580 | 0 | const char *n, *s, *saved_n = NULL; |
581 | 0 | int r, q; |
582 | |
|
583 | 0 | assert(name); |
584 | 0 | assert(suffix); |
585 | |
|
586 | 0 | n = name; |
587 | 0 | s = suffix; |
588 | |
|
589 | 0 | for (;;) { |
590 | 0 | char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; |
591 | |
|
592 | 0 | r = dns_label_unescape(&n, ln, sizeof ln, 0); |
593 | 0 | if (r < 0) |
594 | 0 | return r; |
595 | | |
596 | 0 | if (!saved_n) |
597 | 0 | saved_n = n; |
598 | |
|
599 | 0 | q = dns_label_unescape(&s, ls, sizeof ls, 0); |
600 | 0 | if (q < 0) |
601 | 0 | return q; |
602 | | |
603 | 0 | if (r == 0 && q == 0) |
604 | 0 | return true; |
605 | 0 | if (r == 0 && saved_n == n) |
606 | 0 | return false; |
607 | | |
608 | 0 | if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { |
609 | | |
610 | | /* Not the same, let's jump back, and try with the next label again */ |
611 | 0 | s = suffix; |
612 | 0 | n = TAKE_PTR(saved_n); |
613 | 0 | } |
614 | 0 | } |
615 | 0 | } |
616 | | |
617 | 0 | int dns_name_startswith(const char *name, const char *prefix) { |
618 | 0 | const char *n, *p; |
619 | 0 | int r, q; |
620 | |
|
621 | 0 | assert(name); |
622 | 0 | assert(prefix); |
623 | |
|
624 | 0 | n = name; |
625 | 0 | p = prefix; |
626 | |
|
627 | 0 | for (;;) { |
628 | 0 | char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1]; |
629 | |
|
630 | 0 | r = dns_label_unescape(&p, lp, sizeof lp, 0); |
631 | 0 | if (r < 0) |
632 | 0 | return r; |
633 | 0 | if (r == 0) |
634 | 0 | return true; |
635 | | |
636 | 0 | q = dns_label_unescape(&n, ln, sizeof ln, 0); |
637 | 0 | if (q < 0) |
638 | 0 | return q; |
639 | | |
640 | 0 | if (r != q) |
641 | 0 | return false; |
642 | 0 | if (ascii_strcasecmp_n(ln, lp, r) != 0) |
643 | 0 | return false; |
644 | 0 | } |
645 | 0 | } |
646 | | |
647 | 0 | int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { |
648 | 0 | const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; |
649 | 0 | int r, q; |
650 | |
|
651 | 0 | assert(name); |
652 | 0 | assert(old_suffix); |
653 | 0 | assert(new_suffix); |
654 | 0 | assert(ret); |
655 | |
|
656 | 0 | n = name; |
657 | 0 | s = old_suffix; |
658 | |
|
659 | 0 | for (;;) { |
660 | 0 | char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; |
661 | |
|
662 | 0 | if (!saved_before) |
663 | 0 | saved_before = n; |
664 | |
|
665 | 0 | r = dns_label_unescape(&n, ln, sizeof ln, 0); |
666 | 0 | if (r < 0) |
667 | 0 | return r; |
668 | | |
669 | 0 | if (!saved_after) |
670 | 0 | saved_after = n; |
671 | |
|
672 | 0 | q = dns_label_unescape(&s, ls, sizeof ls, 0); |
673 | 0 | if (q < 0) |
674 | 0 | return q; |
675 | | |
676 | 0 | if (r == 0 && q == 0) |
677 | 0 | break; |
678 | 0 | if (r == 0 && saved_after == n) { |
679 | 0 | *ret = NULL; /* doesn't match */ |
680 | 0 | return 0; |
681 | 0 | } |
682 | | |
683 | 0 | if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { |
684 | | |
685 | | /* Not the same, let's jump back, and try with the next label again */ |
686 | 0 | s = old_suffix; |
687 | 0 | n = TAKE_PTR(saved_after); |
688 | 0 | saved_before = NULL; |
689 | 0 | } |
690 | 0 | } |
691 | | |
692 | | /* Found it! Now generate the new name */ |
693 | 0 | prefix = strndupa_safe(name, saved_before - name); |
694 | |
|
695 | 0 | r = dns_name_concat(prefix, new_suffix, 0, ret); |
696 | 0 | if (r < 0) |
697 | 0 | return r; |
698 | | |
699 | 0 | return 1; |
700 | 0 | } |
701 | | |
702 | 0 | int dns_name_between(const char *a, const char *b, const char *c) { |
703 | | /* Determine if b is strictly greater than a and strictly smaller than c. |
704 | | We consider the order of names to be circular, so that if a is |
705 | | strictly greater than c, we consider b to be between them if it is |
706 | | either greater than a or smaller than c. This is how the canonical |
707 | | DNS name order used in NSEC records work. */ |
708 | |
|
709 | 0 | if (dns_name_compare_func(a, c) < 0) |
710 | | /* |
711 | | a and c are properly ordered: |
712 | | a<---b--->c |
713 | | */ |
714 | 0 | return dns_name_compare_func(a, b) < 0 && |
715 | 0 | dns_name_compare_func(b, c) < 0; |
716 | 0 | else |
717 | | /* |
718 | | a and c are equal or 'reversed': |
719 | | <--b--c a-----> |
720 | | or: |
721 | | <-----c a--b--> |
722 | | */ |
723 | 0 | return dns_name_compare_func(b, c) < 0 || |
724 | 0 | dns_name_compare_func(a, b) < 0; |
725 | 0 | } |
726 | | |
727 | 0 | int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { |
728 | 0 | const uint8_t *p; |
729 | 0 | int r; |
730 | |
|
731 | 0 | assert(a); |
732 | 0 | assert(ret); |
733 | |
|
734 | 0 | p = (const uint8_t*) a; |
735 | |
|
736 | 0 | if (family == AF_INET) |
737 | 0 | r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); |
738 | 0 | else if (family == AF_INET6) |
739 | 0 | 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", |
740 | 0 | hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), |
741 | 0 | hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), |
742 | 0 | hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), |
743 | 0 | hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), |
744 | 0 | hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), |
745 | 0 | hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), |
746 | 0 | hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), |
747 | 0 | hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); |
748 | 0 | else |
749 | 0 | return -EAFNOSUPPORT; |
750 | 0 | if (r < 0) |
751 | 0 | return -ENOMEM; |
752 | | |
753 | 0 | return 0; |
754 | 0 | } |
755 | | |
756 | 0 | int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_address) { |
757 | 0 | int r; |
758 | |
|
759 | 0 | assert(p); |
760 | 0 | assert(ret_family); |
761 | 0 | assert(ret_address); |
762 | |
|
763 | 0 | r = dns_name_endswith(p, "in-addr.arpa"); |
764 | 0 | if (r < 0) |
765 | 0 | return r; |
766 | 0 | if (r > 0) { |
767 | 0 | uint8_t a[4]; |
768 | |
|
769 | 0 | FOREACH_ELEMENT(i, a) { |
770 | 0 | char label[DNS_LABEL_MAX+1]; |
771 | |
|
772 | 0 | r = dns_label_unescape(&p, label, sizeof label, 0); |
773 | 0 | if (r < 0) |
774 | 0 | return r; |
775 | 0 | if (r == 0) |
776 | 0 | return -EINVAL; |
777 | 0 | if (r > 3) |
778 | 0 | return -EINVAL; |
779 | | |
780 | 0 | r = safe_atou8(label, i); |
781 | 0 | if (r < 0) |
782 | 0 | return r; |
783 | 0 | } |
784 | | |
785 | 0 | r = dns_name_equal(p, "in-addr.arpa"); |
786 | 0 | if (r <= 0) |
787 | 0 | return r; |
788 | | |
789 | 0 | *ret_family = AF_INET; |
790 | 0 | ret_address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | |
791 | 0 | ((uint32_t) a[2] << 16) | |
792 | 0 | ((uint32_t) a[1] << 8) | |
793 | 0 | (uint32_t) a[0]); |
794 | |
|
795 | 0 | return 1; |
796 | 0 | } |
797 | | |
798 | 0 | r = dns_name_endswith(p, "ip6.arpa"); |
799 | 0 | if (r < 0) |
800 | 0 | return r; |
801 | 0 | if (r > 0) { |
802 | 0 | struct in6_addr a; |
803 | |
|
804 | 0 | for (size_t i = 0; i < ELEMENTSOF(a.s6_addr); i++) { |
805 | 0 | char label[DNS_LABEL_MAX+1]; |
806 | 0 | int x, y; |
807 | |
|
808 | 0 | r = dns_label_unescape(&p, label, sizeof label, 0); |
809 | 0 | if (r <= 0) |
810 | 0 | return r; |
811 | 0 | if (r != 1) |
812 | 0 | return -EINVAL; |
813 | 0 | x = unhexchar(label[0]); |
814 | 0 | if (x < 0) |
815 | 0 | return -EINVAL; |
816 | | |
817 | 0 | r = dns_label_unescape(&p, label, sizeof label, 0); |
818 | 0 | if (r <= 0) |
819 | 0 | return r; |
820 | 0 | if (r != 1) |
821 | 0 | return -EINVAL; |
822 | 0 | y = unhexchar(label[0]); |
823 | 0 | if (y < 0) |
824 | 0 | return -EINVAL; |
825 | | |
826 | 0 | a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; |
827 | 0 | } |
828 | | |
829 | 0 | r = dns_name_equal(p, "ip6.arpa"); |
830 | 0 | if (r <= 0) |
831 | 0 | return r; |
832 | | |
833 | 0 | *ret_family = AF_INET6; |
834 | 0 | ret_address->in6 = a; |
835 | 0 | return 1; |
836 | 0 | } |
837 | | |
838 | 0 | *ret_family = AF_UNSPEC; |
839 | 0 | *ret_address = IN_ADDR_NULL; |
840 | |
|
841 | 0 | return 0; |
842 | 0 | } |
843 | | |
844 | 791k | bool dns_name_is_root(const char *name) { |
845 | 791k | assert(name); |
846 | | |
847 | | /* There are exactly two ways to encode the root domain name: |
848 | | * as empty string, or with a single dot. */ |
849 | | |
850 | 791k | return STR_IN_SET(name, "", "."); |
851 | 791k | } |
852 | | |
853 | 4.21k | bool dns_name_is_single_label(const char *name) { |
854 | 4.21k | int r; |
855 | | |
856 | 4.21k | assert(name); |
857 | | |
858 | 4.21k | r = dns_name_parent(&name); |
859 | 4.21k | if (r <= 0) |
860 | 0 | return false; |
861 | | |
862 | 4.21k | return dns_name_is_root(name); |
863 | 4.21k | } |
864 | | |
865 | | /* Encode a domain name according to RFC 1035 Section 3.1, without compression */ |
866 | 8.73k | int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { |
867 | 8.73k | uint8_t *label_length, *out; |
868 | 8.73k | int r; |
869 | | |
870 | 8.73k | assert(domain); |
871 | 8.73k | assert(buffer); |
872 | | |
873 | 8.73k | out = buffer; |
874 | | |
875 | 23.3k | do { |
876 | | /* Reserve a byte for label length */ |
877 | 23.3k | if (len <= 0) |
878 | 0 | return -ENOBUFS; |
879 | 23.3k | len--; |
880 | 23.3k | label_length = out; |
881 | 23.3k | out++; |
882 | | |
883 | | /* Convert and copy a single label. Note that |
884 | | * dns_label_unescape() returns 0 when it hits the end |
885 | | * of the domain name, which we rely on here to encode |
886 | | * the trailing NUL byte. */ |
887 | 23.3k | r = dns_label_unescape(&domain, (char *) out, len, 0); |
888 | 23.3k | if (r < 0) |
889 | 0 | return r; |
890 | | |
891 | | /* Optionally, output the name in DNSSEC canonical |
892 | | * format, as described in RFC 4034, section 6.2. Or |
893 | | * in other words: in lower-case. */ |
894 | 23.3k | if (canonical) |
895 | 0 | ascii_strlower_n((char*) out, (size_t) r); |
896 | | |
897 | | /* Fill label length, move forward */ |
898 | 23.3k | *label_length = r; |
899 | 23.3k | out += r; |
900 | 23.3k | len -= r; |
901 | | |
902 | 23.3k | } while (r != 0); |
903 | | |
904 | | /* Verify the maximum size of the encoded name. The trailing |
905 | | * dot + NUL byte account are included this time, hence |
906 | | * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this |
907 | | * time. */ |
908 | 8.73k | if (out - buffer > DNS_HOSTNAME_MAX + 2) |
909 | 0 | return -EINVAL; |
910 | | |
911 | 8.73k | return out - buffer; |
912 | 8.73k | } |
913 | | |
914 | | /* Decode a domain name according to RFC 1035 Section 3.1, without compression */ |
915 | 78.1k | int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) { |
916 | 78.1k | _cleanup_free_ char *domain = NULL; |
917 | 78.1k | const uint8_t *optval; |
918 | 78.1k | size_t optlen, n = 0; |
919 | 78.1k | int r; |
920 | | |
921 | 78.1k | assert(data); |
922 | 78.1k | assert(len); |
923 | 78.1k | assert(*data || *len == 0); |
924 | 78.1k | assert(ret); |
925 | | |
926 | 78.1k | optval = *data; |
927 | 78.1k | optlen = *len; |
928 | | |
929 | 172k | for (;;) { |
930 | 172k | const char *label; |
931 | 172k | uint8_t c; |
932 | | |
933 | | /* RFC 4704 § 4: fully qualified domain names include the terminating |
934 | | * zero-length label, partial names don't. According to the RFC, DHCPv6 |
935 | | * servers should always send the fully qualified name, but that's not |
936 | | * true in practice. Also accept partial names. */ |
937 | 172k | if (optlen == 0) |
938 | 6.28k | break; |
939 | | |
940 | | /* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */ |
941 | 166k | if (*len - optlen > 255) |
942 | 228 | return -EMSGSIZE; |
943 | | |
944 | 165k | c = *optval; |
945 | 165k | optval++; |
946 | 165k | optlen--; |
947 | | |
948 | 165k | if (c == 0) |
949 | | /* End label */ |
950 | 69.5k | break; |
951 | 96.4k | if (c > DNS_LABEL_MAX) |
952 | 1.31k | return -EBADMSG; |
953 | 95.1k | if (c > optlen) |
954 | 845 | return -EMSGSIZE; |
955 | | |
956 | | /* Literal label */ |
957 | 94.3k | label = (const char*) optval; |
958 | 94.3k | optval += c; |
959 | 94.3k | optlen -= c; |
960 | | |
961 | 94.3k | if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) |
962 | 0 | return -ENOMEM; |
963 | | |
964 | 94.3k | if (n != 0) |
965 | 24.9k | domain[n++] = '.'; |
966 | | |
967 | 94.3k | r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); |
968 | 94.3k | if (r < 0) |
969 | 0 | return r; |
970 | | |
971 | 94.3k | n += r; |
972 | 94.3k | } |
973 | | |
974 | 75.7k | if (!GREEDY_REALLOC(domain, n + 1)) |
975 | 0 | return -ENOMEM; |
976 | | |
977 | 75.7k | domain[n] = '\0'; |
978 | | |
979 | 75.7k | *ret = TAKE_PTR(domain); |
980 | 75.7k | *data = optval; |
981 | 75.7k | *len = optlen; |
982 | | |
983 | 75.7k | return n; |
984 | 75.7k | } |
985 | | |
986 | 0 | static bool srv_type_label_is_valid(const char *label, size_t n) { |
987 | 0 | assert(label); |
988 | |
|
989 | 0 | if (n < 2) /* Label needs to be at least 2 chars long */ |
990 | 0 | return false; |
991 | | |
992 | 0 | if (label[0] != '_') /* First label char needs to be underscore */ |
993 | 0 | return false; |
994 | | |
995 | | /* Second char must be a letter */ |
996 | 0 | if (!ascii_isalpha(label[1])) |
997 | 0 | return false; |
998 | | |
999 | | /* Third and further chars must be alphanumeric or a hyphen */ |
1000 | 0 | for (size_t k = 2; k < n; k++) |
1001 | 0 | if (!ascii_isalpha(label[k]) && |
1002 | 0 | !ascii_isdigit(label[k]) && |
1003 | 0 | label[k] != '-') |
1004 | 0 | return false; |
1005 | | |
1006 | 0 | return true; |
1007 | 0 | } |
1008 | | |
1009 | 0 | bool dns_srv_type_is_valid(const char *name) { |
1010 | 0 | unsigned c = 0; |
1011 | 0 | int r; |
1012 | |
|
1013 | 0 | if (!name) |
1014 | 0 | return false; |
1015 | | |
1016 | 0 | for (;;) { |
1017 | 0 | char label[DNS_LABEL_MAX+1]; |
1018 | | |
1019 | | /* This more or less implements RFC 6335, Section 5.1 */ |
1020 | |
|
1021 | 0 | r = dns_label_unescape(&name, label, sizeof label, 0); |
1022 | 0 | if (r < 0) |
1023 | 0 | return false; |
1024 | 0 | if (r == 0) |
1025 | 0 | break; |
1026 | | |
1027 | 0 | if (c >= 2) |
1028 | 0 | return false; |
1029 | | |
1030 | 0 | if (!srv_type_label_is_valid(label, r)) |
1031 | 0 | return false; |
1032 | | |
1033 | 0 | c++; |
1034 | 0 | } |
1035 | | |
1036 | 0 | return c == 2; /* exactly two labels */ |
1037 | 0 | } |
1038 | | |
1039 | 0 | bool dnssd_srv_type_is_valid(const char *name) { |
1040 | 0 | return dns_srv_type_is_valid(name) && |
1041 | 0 | ((dns_name_endswith(name, "_tcp") > 0) || |
1042 | 0 | (dns_name_endswith(name, "_udp") > 0)); /* Specific to DNS-SD. RFC 6763, Section 7 */ |
1043 | 0 | } |
1044 | | |
1045 | 0 | bool dns_service_name_is_valid(const char *name) { |
1046 | 0 | size_t l; |
1047 | | |
1048 | | /* This more or less implements RFC 6763, Section 4.1.1 */ |
1049 | |
|
1050 | 0 | if (!name) |
1051 | 0 | return false; |
1052 | | |
1053 | 0 | if (!utf8_is_valid(name)) |
1054 | 0 | return false; |
1055 | | |
1056 | 0 | if (string_has_cc(name, NULL)) |
1057 | 0 | return false; |
1058 | | |
1059 | 0 | l = strlen(name); |
1060 | 0 | if (l <= 0) |
1061 | 0 | return false; |
1062 | 0 | if (l > DNS_LABEL_MAX) |
1063 | 0 | return false; |
1064 | | |
1065 | 0 | return true; |
1066 | 0 | } |
1067 | | |
1068 | 0 | bool dns_subtype_name_is_valid(const char *name) { |
1069 | 0 | size_t l; |
1070 | | |
1071 | | /* This more or less implements RFC 6763, Section 7.2 */ |
1072 | |
|
1073 | 0 | if (!name) |
1074 | 0 | return false; |
1075 | | |
1076 | 0 | if (!utf8_is_valid(name)) |
1077 | 0 | return false; |
1078 | | |
1079 | 0 | if (string_has_cc(name, NULL)) |
1080 | 0 | return false; |
1081 | | |
1082 | 0 | l = strlen(name); |
1083 | 0 | if (l <= 0) |
1084 | 0 | return false; |
1085 | 0 | if (l > DNS_LABEL_MAX) |
1086 | 0 | return false; |
1087 | | |
1088 | 0 | return true; |
1089 | 0 | } |
1090 | | |
1091 | 0 | int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { |
1092 | 0 | char escaped[DNS_LABEL_ESCAPED_MAX]; |
1093 | 0 | _cleanup_free_ char *n = NULL; |
1094 | 0 | int r; |
1095 | |
|
1096 | 0 | assert(type); |
1097 | 0 | assert(domain); |
1098 | 0 | assert(ret); |
1099 | |
|
1100 | 0 | if (!dns_srv_type_is_valid(type)) |
1101 | 0 | return -EINVAL; |
1102 | | |
1103 | 0 | if (!name) |
1104 | 0 | return dns_name_concat(type, domain, 0, ret); |
1105 | | |
1106 | 0 | if (!dns_service_name_is_valid(name)) |
1107 | 0 | return -EINVAL; |
1108 | | |
1109 | 0 | r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); |
1110 | 0 | if (r < 0) |
1111 | 0 | return r; |
1112 | | |
1113 | 0 | r = dns_name_concat(type, domain, 0, &n); |
1114 | 0 | if (r < 0) |
1115 | 0 | return r; |
1116 | | |
1117 | 0 | return dns_name_concat(escaped, n, 0, ret); |
1118 | 0 | } |
1119 | | |
1120 | 0 | static bool dns_service_name_label_is_valid(const char *label, size_t n) { |
1121 | 0 | char *s; |
1122 | |
|
1123 | 0 | assert(label); |
1124 | |
|
1125 | 0 | if (memchr(label, 0, n)) |
1126 | 0 | return false; |
1127 | | |
1128 | 0 | s = strndupa_safe(label, n); |
1129 | 0 | return dns_service_name_is_valid(s); |
1130 | 0 | } |
1131 | | |
1132 | 0 | int dns_service_split(const char *joined, char **ret_name, char **ret_type, char **ret_domain) { |
1133 | 0 | _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; |
1134 | 0 | const char *p = joined, *q = NULL, *d = joined; |
1135 | 0 | char a[DNS_LABEL_MAX+1], b[DNS_LABEL_MAX+1], c[DNS_LABEL_MAX+1]; |
1136 | 0 | int an, bn, cn, r; |
1137 | 0 | unsigned x = 0; |
1138 | |
|
1139 | 0 | assert(joined); |
1140 | | |
1141 | | /* Get first label from the full name */ |
1142 | 0 | an = dns_label_unescape(&p, a, sizeof(a), 0); |
1143 | 0 | if (an < 0) |
1144 | 0 | return an; |
1145 | | |
1146 | 0 | if (an > 0) { |
1147 | 0 | x++; |
1148 | | |
1149 | | /* If there was a first label, try to get the second one */ |
1150 | 0 | bn = dns_label_unescape(&p, b, sizeof(b), 0); |
1151 | 0 | if (bn < 0) |
1152 | 0 | return bn; |
1153 | | |
1154 | 0 | if (bn > 0) { |
1155 | 0 | if (!srv_type_label_is_valid(b, bn)) |
1156 | 0 | goto finish; |
1157 | | |
1158 | 0 | x++; |
1159 | | |
1160 | | /* If there was a second label, try to get the third one */ |
1161 | 0 | q = p; |
1162 | 0 | cn = dns_label_unescape(&p, c, sizeof(c), 0); |
1163 | 0 | if (cn < 0) |
1164 | 0 | return cn; |
1165 | | |
1166 | 0 | if (cn > 0 && srv_type_label_is_valid(c, cn)) |
1167 | 0 | x++; |
1168 | 0 | } |
1169 | 0 | } |
1170 | | |
1171 | 0 | switch (x) { |
1172 | 0 | case 2: |
1173 | 0 | if (!srv_type_label_is_valid(a, an)) |
1174 | 0 | break; |
1175 | | |
1176 | | /* OK, got <type> . <type2> . <domain> */ |
1177 | | |
1178 | 0 | name = NULL; |
1179 | |
|
1180 | 0 | type = strjoin(a, ".", b); |
1181 | 0 | if (!type) |
1182 | 0 | return -ENOMEM; |
1183 | | |
1184 | 0 | d = q; |
1185 | 0 | break; |
1186 | | |
1187 | 0 | case 3: |
1188 | 0 | if (!dns_service_name_label_is_valid(a, an)) |
1189 | 0 | break; |
1190 | | |
1191 | | /* OK, got <name> . <type> . <type2> . <domain> */ |
1192 | | |
1193 | 0 | name = strndup(a, an); |
1194 | 0 | if (!name) |
1195 | 0 | return -ENOMEM; |
1196 | | |
1197 | 0 | type = strjoin(b, ".", c); |
1198 | 0 | if (!type) |
1199 | 0 | return -ENOMEM; |
1200 | | |
1201 | 0 | d = p; |
1202 | 0 | break; |
1203 | 0 | } |
1204 | | |
1205 | 0 | finish: |
1206 | 0 | r = dns_name_normalize(d, 0, &domain); |
1207 | 0 | if (r < 0) |
1208 | 0 | return r; |
1209 | | |
1210 | 0 | if (ret_domain) |
1211 | 0 | *ret_domain = TAKE_PTR(domain); |
1212 | |
|
1213 | 0 | if (ret_type) |
1214 | 0 | *ret_type = TAKE_PTR(type); |
1215 | |
|
1216 | 0 | if (ret_name) |
1217 | 0 | *ret_name = TAKE_PTR(name); |
1218 | |
|
1219 | 0 | return 0; |
1220 | 0 | } |
1221 | | |
1222 | 0 | static int dns_name_build_suffix_table(const char *name, const char *table[]) { |
1223 | 0 | const char *p = ASSERT_PTR(name); |
1224 | 0 | unsigned n = 0; |
1225 | 0 | int r; |
1226 | |
|
1227 | 0 | assert(table); |
1228 | |
|
1229 | 0 | for (;;) { |
1230 | 0 | if (n > DNS_N_LABELS_MAX) |
1231 | 0 | return -EINVAL; |
1232 | | |
1233 | 0 | table[n] = p; |
1234 | 0 | r = dns_name_parent(&p); |
1235 | 0 | if (r < 0) |
1236 | 0 | return r; |
1237 | 0 | if (r == 0) |
1238 | 0 | break; |
1239 | | |
1240 | 0 | n++; |
1241 | 0 | } |
1242 | | |
1243 | 0 | return (int) n; |
1244 | 0 | } |
1245 | | |
1246 | 0 | int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { |
1247 | 0 | const char* labels[DNS_N_LABELS_MAX+1]; |
1248 | 0 | int n; |
1249 | |
|
1250 | 0 | assert(name); |
1251 | 0 | assert(ret); |
1252 | |
|
1253 | 0 | n = dns_name_build_suffix_table(name, labels); |
1254 | 0 | if (n < 0) |
1255 | 0 | return n; |
1256 | | |
1257 | 0 | if ((unsigned) n < n_labels) |
1258 | 0 | return -EINVAL; |
1259 | | |
1260 | 0 | *ret = labels[n - n_labels]; |
1261 | 0 | return (int) (n - n_labels); |
1262 | 0 | } |
1263 | | |
1264 | 0 | int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { |
1265 | 0 | int r; |
1266 | |
|
1267 | 0 | assert(a); |
1268 | 0 | assert(ret); |
1269 | |
|
1270 | 0 | for (; n_labels > 0; n_labels--) { |
1271 | 0 | r = dns_name_parent(&a); |
1272 | 0 | if (r < 0) |
1273 | 0 | return r; |
1274 | 0 | if (r == 0) { |
1275 | 0 | *ret = ""; |
1276 | 0 | return 0; |
1277 | 0 | } |
1278 | 0 | } |
1279 | | |
1280 | 0 | *ret = a; |
1281 | 0 | return 1; |
1282 | 0 | } |
1283 | | |
1284 | 0 | int dns_name_count_labels(const char *name) { |
1285 | 0 | unsigned n = 0; |
1286 | 0 | int r; |
1287 | |
|
1288 | 0 | assert(name); |
1289 | |
|
1290 | 0 | for (const char *p = name;;) { |
1291 | 0 | r = dns_name_parent(&p); |
1292 | 0 | if (r < 0) |
1293 | 0 | return r; |
1294 | 0 | if (r == 0) |
1295 | 0 | break; |
1296 | | |
1297 | 0 | if (n >= DNS_N_LABELS_MAX) |
1298 | 0 | return -EINVAL; |
1299 | | |
1300 | 0 | n++; |
1301 | 0 | } |
1302 | | |
1303 | 0 | return n; |
1304 | 0 | } |
1305 | | |
1306 | 0 | int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { |
1307 | 0 | int r; |
1308 | |
|
1309 | 0 | assert(a); |
1310 | 0 | assert(b); |
1311 | |
|
1312 | 0 | r = dns_name_skip(a, n_labels, &a); |
1313 | 0 | if (r <= 0) |
1314 | 0 | return r; |
1315 | | |
1316 | 0 | return dns_name_equal(a, b); |
1317 | 0 | } |
1318 | | |
1319 | 0 | int dns_name_common_suffix(const char *a, const char *b, const char **ret) { |
1320 | 0 | const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; |
1321 | 0 | int n = 0, m = 0, k = 0, r, q; |
1322 | |
|
1323 | 0 | assert(a); |
1324 | 0 | assert(b); |
1325 | 0 | assert(ret); |
1326 | | |
1327 | | /* Determines the common suffix of domain names a and b */ |
1328 | |
|
1329 | 0 | n = dns_name_build_suffix_table(a, a_labels); |
1330 | 0 | if (n < 0) |
1331 | 0 | return n; |
1332 | | |
1333 | 0 | m = dns_name_build_suffix_table(b, b_labels); |
1334 | 0 | if (m < 0) |
1335 | 0 | return m; |
1336 | | |
1337 | 0 | for (;;) { |
1338 | 0 | char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; |
1339 | 0 | const char *x, *y; |
1340 | |
|
1341 | 0 | if (k >= n || k >= m) { |
1342 | 0 | *ret = a_labels[n - k]; |
1343 | 0 | return 0; |
1344 | 0 | } |
1345 | | |
1346 | 0 | x = a_labels[n - 1 - k]; |
1347 | 0 | r = dns_label_unescape(&x, la, sizeof la, 0); |
1348 | 0 | if (r < 0) |
1349 | 0 | return r; |
1350 | | |
1351 | 0 | y = b_labels[m - 1 - k]; |
1352 | 0 | q = dns_label_unescape(&y, lb, sizeof lb, 0); |
1353 | 0 | if (q < 0) |
1354 | 0 | return q; |
1355 | | |
1356 | 0 | if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { |
1357 | 0 | *ret = a_labels[n - k]; |
1358 | 0 | return 0; |
1359 | 0 | } |
1360 | | |
1361 | 0 | k++; |
1362 | 0 | } |
1363 | 0 | } |
1364 | | |
1365 | 840 | int dns_name_apply_idna(const char *name, char **ret) { |
1366 | | |
1367 | | /* Return negative on error, 0 if not implemented, positive on success. */ |
1368 | | |
1369 | | #if HAVE_LIBIDN2 || HAVE_LIBIDN2 |
1370 | | int r; |
1371 | | |
1372 | | r = dlopen_idn(); |
1373 | | if (r == -EOPNOTSUPP) { |
1374 | | *ret = NULL; |
1375 | | return 0; |
1376 | | } |
1377 | | if (r < 0) |
1378 | | return r; |
1379 | | #endif |
1380 | | |
1381 | | #if HAVE_LIBIDN2 |
1382 | | _cleanup_free_ char *t = NULL; |
1383 | | |
1384 | | assert(name); |
1385 | | assert(ret); |
1386 | | |
1387 | | /* First, try non-transitional mode (i.e. IDN2008 rules) */ |
1388 | | r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, |
1389 | | IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); |
1390 | | if (r == IDN2_DISALLOWED) /* If that failed, because of disallowed characters, try transitional mode. |
1391 | | * (i.e. IDN2003 rules which supports some unicode chars IDN2008 doesn't allow). */ |
1392 | | r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, |
1393 | | IDN2_NFC_INPUT | IDN2_TRANSITIONAL); |
1394 | | |
1395 | | log_debug("idn2_lookup_u8: %s %s %s", name, glyph(GLYPH_ARROW_RIGHT), t); |
1396 | | if (r == IDN2_OK) { |
1397 | | if (!startswith(name, "xn--")) { |
1398 | | _cleanup_free_ char *s = NULL; |
1399 | | |
1400 | | r = sym_idn2_to_unicode_8z8z(t, &s, 0); |
1401 | | if (r != IDN2_OK) { |
1402 | | log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s", |
1403 | | t, r, sym_idn2_strerror(r)); |
1404 | | *ret = NULL; |
1405 | | return 0; |
1406 | | } |
1407 | | |
1408 | | if (!streq_ptr(name, s)) { |
1409 | | log_debug("idn2 roundtrip failed: \"%s\" %s \"%s\" %s \"%s\", ignoring.", |
1410 | | name, glyph(GLYPH_ARROW_RIGHT), t, |
1411 | | glyph(GLYPH_ARROW_RIGHT), s); |
1412 | | *ret = NULL; |
1413 | | return 0; |
1414 | | } |
1415 | | } |
1416 | | |
1417 | | *ret = TAKE_PTR(t); |
1418 | | return 1; /* *ret has been written */ |
1419 | | } |
1420 | | |
1421 | | log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, sym_idn2_strerror(r)); |
1422 | | if (r == IDN2_2HYPHEN) |
1423 | | /* The name has two hyphens — forbidden by IDNA2008 in some cases */ |
1424 | | return 0; |
1425 | | if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL)) |
1426 | | return -ENOSPC; |
1427 | | |
1428 | | return -EINVAL; |
1429 | | #elif HAVE_LIBIDN |
1430 | | _cleanup_free_ char *buf = NULL; |
1431 | | size_t n = 0; |
1432 | | bool first = true; |
1433 | | int r, q; |
1434 | | |
1435 | | assert(name); |
1436 | | assert(ret); |
1437 | | |
1438 | | for (;;) { |
1439 | | char label[DNS_LABEL_MAX+1]; |
1440 | | |
1441 | | r = dns_label_unescape(&name, label, sizeof label, 0); |
1442 | | if (r < 0) |
1443 | | return r; |
1444 | | if (r == 0) |
1445 | | break; |
1446 | | |
1447 | | q = dns_label_apply_idna(label, r, label, sizeof label); |
1448 | | if (q < 0) |
1449 | | return q; |
1450 | | if (q > 0) |
1451 | | r = q; |
1452 | | |
1453 | | if (!GREEDY_REALLOC(buf, n + !first + DNS_LABEL_ESCAPED_MAX)) |
1454 | | return -ENOMEM; |
1455 | | |
1456 | | r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); |
1457 | | if (r < 0) |
1458 | | return r; |
1459 | | |
1460 | | if (first) |
1461 | | first = false; |
1462 | | else |
1463 | | buf[n++] = '.'; |
1464 | | |
1465 | | n += r; |
1466 | | } |
1467 | | |
1468 | | if (n > DNS_HOSTNAME_MAX) |
1469 | | return -EINVAL; |
1470 | | |
1471 | | if (!GREEDY_REALLOC(buf, n + 1)) |
1472 | | return -ENOMEM; |
1473 | | |
1474 | | buf[n] = 0; |
1475 | | *ret = TAKE_PTR(buf); |
1476 | | |
1477 | | return 1; |
1478 | | #else |
1479 | 840 | *ret = NULL; |
1480 | 840 | return 0; |
1481 | 840 | #endif |
1482 | 840 | } |
1483 | | |
1484 | 4.72k | int dns_name_is_valid_or_address(const char *name) { |
1485 | | /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */ |
1486 | | |
1487 | 4.72k | if (isempty(name)) |
1488 | 0 | return 0; |
1489 | | |
1490 | 4.72k | if (in_addr_from_string_auto(name, NULL, NULL) >= 0) |
1491 | 407 | return 1; |
1492 | | |
1493 | 4.31k | return dns_name_is_valid(name); |
1494 | 4.72k | } |
1495 | | |
1496 | 0 | int dns_name_dot_suffixed(const char *name) { |
1497 | 0 | const char *p = name; |
1498 | 0 | int r; |
1499 | |
|
1500 | 0 | for (;;) { |
1501 | 0 | if (streq(p, ".")) |
1502 | 0 | return true; |
1503 | | |
1504 | 0 | r = dns_label_unescape(&p, NULL, DNS_LABEL_MAX, DNS_LABEL_LEAVE_TRAILING_DOT); |
1505 | 0 | if (r < 0) |
1506 | 0 | return r; |
1507 | 0 | if (r == 0) |
1508 | 0 | return false; |
1509 | 0 | } |
1510 | 0 | } |
1511 | | |
1512 | 0 | bool dns_name_dont_resolve(const char *name) { |
1513 | | |
1514 | | /* Never respond to some of the domains listed in RFC6303 */ |
1515 | 0 | if (dns_name_endswith(name, "0.in-addr.arpa") > 0 || |
1516 | 0 | dns_name_equal(name, "255.255.255.255.in-addr.arpa") > 0 || |
1517 | 0 | dns_name_equal(name, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) |
1518 | 0 | return true; |
1519 | | |
1520 | | /* Never respond to some of the domains listed in RFC6761 */ |
1521 | 0 | if (dns_name_endswith(name, "invalid") > 0) |
1522 | 0 | return true; |
1523 | | |
1524 | | /* Never respond to some of the domains listed in RFC9476 */ |
1525 | 0 | if (dns_name_endswith(name, "alt") > 0) |
1526 | 0 | return true; |
1527 | | |
1528 | 0 | return false; |
1529 | 0 | } |