1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
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.
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.
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/>.
24 #include <stringprep.h>
27 #include "string-util.h"
28 #include "dns-domain.h"
30 int dns_label_unescape(const char **name
, char *dest
, size_t sz
) {
54 if (r
>= DNS_LABEL_MAX
)
58 /* Escaped character */
66 else if (*n
== '\\' || *n
== '.') {
67 /* Escaped backslash or dot */
72 } else if (n
[0] >= '0' && n
[0] <= '9') {
75 /* Escaped literal ASCII character */
77 if (!(n
[1] >= '0' && n
[1] <= '9') ||
78 !(n
[2] >= '0' && n
[2] <= '9'))
81 k
= ((unsigned) (n
[0] - '0') * 100) +
82 ((unsigned) (n
[1] - '0') * 10) +
83 ((unsigned) (n
[2] - '0'));
85 /* Don't allow CC characters or anything that doesn't fit in 8bit */
86 if (k
< ' ' || k
> 255 || k
== 127)
97 } else if ((uint8_t) *n
>= (uint8_t) ' ' && *n
!= 127) {
99 /* Normal character */
107 /* Empty label that is not at the end? */
118 /* @label_terminal: terminal character of a label, updated to point to the terminal character of
119 * the previous label (always skipping one dot) or to NULL if there are no more
121 int dns_label_unescape_suffix(const char *name
, const char **label_terminal
, char *dest
, size_t sz
) {
122 const char *terminal
;
126 assert(label_terminal
);
130 if (!*label_terminal
) {
137 assert(**label_terminal
== '.' || **label_terminal
== 0);
139 /* skip current terminal character */
140 terminal
= *label_terminal
- 1;
142 /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
144 if (terminal
< name
) {
145 /* reached the first label, so indicate that there are no more */
150 /* find the start of the last label */
151 if (*terminal
== '.') {
153 unsigned slashes
= 0;
155 for (y
= terminal
- 1; y
>= name
&& *y
== '\\'; y
--)
158 if (slashes
% 2 == 0) {
159 /* the '.' was not escaped */
171 r
= dns_label_unescape(&name
, dest
, sz
);
175 *label_terminal
= terminal
;
180 int dns_label_escape(const char *p
, size_t l
, char **ret
) {
181 _cleanup_free_
char *s
= NULL
;
188 if (l
> DNS_LABEL_MAX
)
191 s
= malloc(l
* 4 + 1);
198 if (*p
== '.' || *p
== '\\') {
200 /* Dot or backslash */
204 } else if (*p
== '_' ||
206 (*p
>= '0' && *p
<= '9') ||
207 (*p
>= 'a' && *p
<= 'z') ||
208 (*p
>= 'A' && *p
<= 'Z')) {
210 /* Proper character */
212 } else if ((uint8_t) *p
>= (uint8_t) ' ' && *p
!= 127) {
214 /* Everything else */
216 *(q
++) = '0' + (char) ((uint8_t) *p
/ 100);
217 *(q
++) = '0' + (char) (((uint8_t) *p
/ 10) % 10);
218 *(q
++) = '0' + (char) ((uint8_t) *p
% 10);
235 int dns_label_apply_idna(const char *encoded
, size_t encoded_size
, char *decoded
, size_t decoded_max
) {
237 _cleanup_free_
uint32_t *input
= NULL
;
240 bool contains_8bit
= false;
244 assert(decoded_max
>= DNS_LABEL_MAX
);
246 if (encoded_size
<= 0)
249 for (p
= encoded
; p
< encoded
+ encoded_size
; p
++)
250 if ((uint8_t) *p
> 127)
251 contains_8bit
= true;
256 input
= stringprep_utf8_to_ucs4(encoded
, encoded_size
, &input_size
);
260 if (idna_to_ascii_4i(input
, input_size
, decoded
, 0) != 0)
263 return strlen(decoded
);
269 int dns_label_undo_idna(const char *encoded
, size_t encoded_size
, char *decoded
, size_t decoded_max
) {
271 size_t input_size
, output_size
;
272 _cleanup_free_
uint32_t *input
= NULL
;
273 _cleanup_free_
char *result
= NULL
;
274 uint32_t *output
= NULL
;
277 /* To be invoked after unescaping */
282 if (encoded_size
< sizeof(IDNA_ACE_PREFIX
)-1)
285 if (memcmp(encoded
, IDNA_ACE_PREFIX
, sizeof(IDNA_ACE_PREFIX
) -1) != 0)
288 input
= stringprep_utf8_to_ucs4(encoded
, encoded_size
, &input_size
);
292 output_size
= input_size
;
293 output
= newa(uint32_t, output_size
);
295 idna_to_unicode_44i(input
, input_size
, output
, &output_size
, 0);
297 result
= stringprep_ucs4_to_utf8(output
, output_size
, NULL
, &w
);
302 if (w
+1 > decoded_max
)
305 memcpy(decoded
, result
, w
+1);
312 int dns_name_concat(const char *a
, const char *b
, char **_ret
) {
313 _cleanup_free_
char *ret
= NULL
;
314 size_t n
= 0, allocated
= 0;
322 _cleanup_free_
char *t
= NULL
;
323 char label
[DNS_LABEL_MAX
];
326 r
= dns_label_unescape(&p
, label
, sizeof(label
));
334 /* Now continue with the second string, if there is one */
343 k
= dns_label_undo_idna(label
, r
, label
, sizeof(label
));
349 r
= dns_label_escape(label
, r
, &t
);
354 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ strlen(t
) + 1))
362 memcpy(ret
+ n
, t
, r
);
368 if (n
> DNS_NAME_MAX
)
372 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
383 void dns_name_hash_func(const void *s
, struct siphash
*state
) {
390 char label
[DNS_LABEL_MAX
+1];
393 r
= dns_label_unescape(&p
, label
, sizeof(label
));
397 k
= dns_label_undo_idna(label
, r
, label
, sizeof(label
));
407 ascii_strlower(label
);
409 string_hash_func(label
, state
);
412 /* enforce that all names are terminated by the empty label */
413 string_hash_func("", state
);
416 int dns_name_compare_func(const void *a
, const void *b
) {
423 x
= (const char *) a
+ strlen(a
);
424 y
= (const char *) b
+ strlen(b
);
427 char la
[DNS_LABEL_MAX
+1], lb
[DNS_LABEL_MAX
+1];
429 if (x
== NULL
&& y
== NULL
)
432 r
= dns_label_unescape_suffix(a
, &x
, la
, sizeof(la
));
433 q
= dns_label_unescape_suffix(b
, &y
, lb
, sizeof(lb
));
437 k
= dns_label_undo_idna(la
, r
, la
, sizeof(la
));
438 w
= dns_label_undo_idna(lb
, q
, lb
, sizeof(lb
));
447 r
= strcasecmp(la
, lb
);
453 const struct hash_ops dns_name_hash_ops
= {
454 .hash
= dns_name_hash_func
,
455 .compare
= dns_name_compare_func
458 int dns_name_equal(const char *x
, const char *y
) {
465 char la
[DNS_LABEL_MAX
+1], lb
[DNS_LABEL_MAX
+1];
467 if (*x
== 0 && *y
== 0)
470 r
= dns_label_unescape(&x
, la
, sizeof(la
));
474 k
= dns_label_undo_idna(la
, r
, la
, sizeof(la
));
480 q
= dns_label_unescape(&y
, lb
, sizeof(lb
));
483 w
= dns_label_undo_idna(lb
, q
, lb
, sizeof(lb
));
490 if (strcasecmp(la
, lb
))
495 int dns_name_endswith(const char *name
, const char *suffix
) {
496 const char *n
, *s
, *saved_n
= NULL
;
506 char ln
[DNS_LABEL_MAX
+1], ls
[DNS_LABEL_MAX
+1];
508 r
= dns_label_unescape(&n
, ln
, sizeof(ln
));
511 k
= dns_label_undo_idna(ln
, r
, ln
, sizeof(ln
));
520 q
= dns_label_unescape(&s
, ls
, sizeof(ls
));
523 w
= dns_label_undo_idna(ls
, q
, ls
, sizeof(ls
));
529 if (r
== 0 && q
== 0)
531 if (r
== 0 && saved_n
== n
)
536 if (r
!= q
|| strcasecmp(ln
, ls
)) {
538 /* Not the same, let's jump back, and try with the next label again */
546 int dns_name_between(const char *a
, const char *b
, const char *c
) {
549 /* Determine if b is strictly greater than a and strictly smaller than c.
550 We consider the order of names to be circular, so that if a is
551 strictly greater than c, we consider b to be between them if it is
552 either greater than a or smaller than c. This is how the canonical
553 DNS name order used in NSEC records work. */
555 n
= dns_name_compare_func(a
, c
);
560 return dns_name_compare_func(a
, b
) < 0 &&
561 dns_name_compare_func(b
, c
) < 0;
563 /* <--b--c a--b--> */
564 return dns_name_compare_func(b
, c
) < 0 ||
565 dns_name_compare_func(a
, b
) < 0;
568 int dns_name_reverse(int family
, const union in_addr_union
*a
, char **ret
) {
575 p
= (const uint8_t*) a
;
577 if (family
== AF_INET
)
578 r
= asprintf(ret
, "%u.%u.%u.%u.in-addr.arpa", p
[3], p
[2], p
[1], p
[0]);
579 else if (family
== AF_INET6
)
580 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",
581 hexchar(p
[15] & 0xF), hexchar(p
[15] >> 4), hexchar(p
[14] & 0xF), hexchar(p
[14] >> 4),
582 hexchar(p
[13] & 0xF), hexchar(p
[13] >> 4), hexchar(p
[12] & 0xF), hexchar(p
[12] >> 4),
583 hexchar(p
[11] & 0xF), hexchar(p
[11] >> 4), hexchar(p
[10] & 0xF), hexchar(p
[10] >> 4),
584 hexchar(p
[ 9] & 0xF), hexchar(p
[ 9] >> 4), hexchar(p
[ 8] & 0xF), hexchar(p
[ 8] >> 4),
585 hexchar(p
[ 7] & 0xF), hexchar(p
[ 7] >> 4), hexchar(p
[ 6] & 0xF), hexchar(p
[ 6] >> 4),
586 hexchar(p
[ 5] & 0xF), hexchar(p
[ 5] >> 4), hexchar(p
[ 4] & 0xF), hexchar(p
[ 4] >> 4),
587 hexchar(p
[ 3] & 0xF), hexchar(p
[ 3] >> 4), hexchar(p
[ 2] & 0xF), hexchar(p
[ 2] >> 4),
588 hexchar(p
[ 1] & 0xF), hexchar(p
[ 1] >> 4), hexchar(p
[ 0] & 0xF), hexchar(p
[ 0] >> 4));
590 return -EAFNOSUPPORT
;
597 int dns_name_address(const char *p
, int *family
, union in_addr_union
*address
) {
604 r
= dns_name_endswith(p
, "in-addr.arpa");
611 for (i
= 0; i
< ELEMENTSOF(a
); i
++) {
612 char label
[DNS_LABEL_MAX
+1];
614 r
= dns_label_unescape(&p
, label
, sizeof(label
));
622 r
= safe_atou8(label
, &a
[i
]);
627 r
= dns_name_equal(p
, "in-addr.arpa");
632 address
->in
.s_addr
= htobe32(((uint32_t) a
[3] << 24) |
633 ((uint32_t) a
[2] << 16) |
634 ((uint32_t) a
[1] << 8) |
640 r
= dns_name_endswith(p
, "ip6.arpa");
647 for (i
= 0; i
< ELEMENTSOF(a
.s6_addr
); i
++) {
648 char label
[DNS_LABEL_MAX
+1];
651 r
= dns_label_unescape(&p
, label
, sizeof(label
));
656 x
= unhexchar(label
[0]);
660 r
= dns_label_unescape(&p
, label
, sizeof(label
));
665 y
= unhexchar(label
[0]);
669 a
.s6_addr
[ELEMENTSOF(a
.s6_addr
) - i
- 1] = (uint8_t) y
<< 4 | (uint8_t) x
;
672 r
= dns_name_equal(p
, "ip6.arpa");
684 int dns_name_root(const char *name
) {
685 char label
[DNS_LABEL_MAX
+1];
690 r
= dns_label_unescape(&name
, label
, sizeof(label
));
694 return r
== 0 && *name
== 0;
697 int dns_name_single_label(const char *name
) {
698 char label
[DNS_LABEL_MAX
+1];
703 r
= dns_label_unescape(&name
, label
, sizeof(label
));
709 r
= dns_label_unescape(&name
, label
, sizeof(label
));
713 return r
== 0 && *name
== 0;