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 "alloc-util.h"
28 #include "dns-domain.h"
29 #include "hexdecoct.h"
30 #include "parse-util.h"
31 #include "string-util.h"
33 int dns_label_unescape(const char **name
, char *dest
, size_t sz
) {
57 if (r
>= DNS_LABEL_MAX
)
61 /* Escaped character */
69 else if (*n
== '\\' || *n
== '.') {
70 /* Escaped backslash or dot */
75 } else if (n
[0] >= '0' && n
[0] <= '9') {
78 /* Escaped literal ASCII character */
80 if (!(n
[1] >= '0' && n
[1] <= '9') ||
81 !(n
[2] >= '0' && n
[2] <= '9'))
84 k
= ((unsigned) (n
[0] - '0') * 100) +
85 ((unsigned) (n
[1] - '0') * 10) +
86 ((unsigned) (n
[2] - '0'));
88 /* Don't allow CC characters or anything that doesn't fit in 8bit */
89 if (k
< ' ' || k
> 255 || k
== 127)
100 } else if ((uint8_t) *n
>= (uint8_t) ' ' && *n
!= 127) {
102 /* Normal character */
110 /* Empty label that is not at the end? */
121 /* @label_terminal: terminal character of a label, updated to point to the terminal character of
122 * the previous label (always skipping one dot) or to NULL if there are no more
124 int dns_label_unescape_suffix(const char *name
, const char **label_terminal
, char *dest
, size_t sz
) {
125 const char *terminal
;
129 assert(label_terminal
);
133 if (!*label_terminal
) {
140 assert(**label_terminal
== '.' || **label_terminal
== 0);
142 /* skip current terminal character */
143 terminal
= *label_terminal
- 1;
145 /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
147 if (terminal
< name
) {
148 /* reached the first label, so indicate that there are no more */
153 /* find the start of the last label */
154 if (*terminal
== '.') {
156 unsigned slashes
= 0;
158 for (y
= terminal
- 1; y
>= name
&& *y
== '\\'; y
--)
161 if (slashes
% 2 == 0) {
162 /* the '.' was not escaped */
174 r
= dns_label_unescape(&name
, dest
, sz
);
178 *label_terminal
= terminal
;
183 int dns_label_escape(const char *p
, size_t l
, char **ret
) {
184 _cleanup_free_
char *s
= NULL
;
191 if (l
> DNS_LABEL_MAX
)
194 s
= malloc(l
* 4 + 1);
201 if (*p
== '.' || *p
== '\\') {
203 /* Dot or backslash */
207 } else if (*p
== '_' ||
209 (*p
>= '0' && *p
<= '9') ||
210 (*p
>= 'a' && *p
<= 'z') ||
211 (*p
>= 'A' && *p
<= 'Z')) {
213 /* Proper character */
215 } else if ((uint8_t) *p
>= (uint8_t) ' ' && *p
!= 127) {
217 /* Everything else */
219 *(q
++) = '0' + (char) ((uint8_t) *p
/ 100);
220 *(q
++) = '0' + (char) (((uint8_t) *p
/ 10) % 10);
221 *(q
++) = '0' + (char) ((uint8_t) *p
% 10);
238 int dns_label_apply_idna(const char *encoded
, size_t encoded_size
, char *decoded
, size_t decoded_max
) {
240 _cleanup_free_
uint32_t *input
= NULL
;
243 bool contains_8bit
= false;
247 assert(decoded_max
>= DNS_LABEL_MAX
);
249 if (encoded_size
<= 0)
252 for (p
= encoded
; p
< encoded
+ encoded_size
; p
++)
253 if ((uint8_t) *p
> 127)
254 contains_8bit
= true;
259 input
= stringprep_utf8_to_ucs4(encoded
, encoded_size
, &input_size
);
263 if (idna_to_ascii_4i(input
, input_size
, decoded
, 0) != 0)
266 return strlen(decoded
);
272 int dns_label_undo_idna(const char *encoded
, size_t encoded_size
, char *decoded
, size_t decoded_max
) {
274 size_t input_size
, output_size
;
275 _cleanup_free_
uint32_t *input
= NULL
;
276 _cleanup_free_
char *result
= NULL
;
277 uint32_t *output
= NULL
;
280 /* To be invoked after unescaping */
285 if (encoded_size
< sizeof(IDNA_ACE_PREFIX
)-1)
288 if (memcmp(encoded
, IDNA_ACE_PREFIX
, sizeof(IDNA_ACE_PREFIX
) -1) != 0)
291 input
= stringprep_utf8_to_ucs4(encoded
, encoded_size
, &input_size
);
295 output_size
= input_size
;
296 output
= newa(uint32_t, output_size
);
298 idna_to_unicode_44i(input
, input_size
, output
, &output_size
, 0);
300 result
= stringprep_ucs4_to_utf8(output
, output_size
, NULL
, &w
);
305 if (w
+1 > decoded_max
)
308 memcpy(decoded
, result
, w
+1);
315 int dns_name_concat(const char *a
, const char *b
, char **_ret
) {
316 _cleanup_free_
char *ret
= NULL
;
317 size_t n
= 0, allocated
= 0;
325 _cleanup_free_
char *t
= NULL
;
326 char label
[DNS_LABEL_MAX
];
329 r
= dns_label_unescape(&p
, label
, sizeof(label
));
337 /* Now continue with the second string, if there is one */
346 k
= dns_label_undo_idna(label
, r
, label
, sizeof(label
));
352 r
= dns_label_escape(label
, r
, &t
);
357 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ strlen(t
) + 1))
365 memcpy(ret
+ n
, t
, r
);
371 if (n
> DNS_NAME_MAX
)
375 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
386 void dns_name_hash_func(const void *s
, struct siphash
*state
) {
393 char label
[DNS_LABEL_MAX
+1];
396 r
= dns_label_unescape(&p
, label
, sizeof(label
));
400 k
= dns_label_undo_idna(label
, r
, label
, sizeof(label
));
410 ascii_strlower(label
);
412 string_hash_func(label
, state
);
415 /* enforce that all names are terminated by the empty label */
416 string_hash_func("", state
);
419 int dns_name_compare_func(const void *a
, const void *b
) {
426 x
= (const char *) a
+ strlen(a
);
427 y
= (const char *) b
+ strlen(b
);
430 char la
[DNS_LABEL_MAX
+1], lb
[DNS_LABEL_MAX
+1];
432 if (x
== NULL
&& y
== NULL
)
435 r
= dns_label_unescape_suffix(a
, &x
, la
, sizeof(la
));
436 q
= dns_label_unescape_suffix(b
, &y
, lb
, sizeof(lb
));
440 k
= dns_label_undo_idna(la
, r
, la
, sizeof(la
));
441 w
= dns_label_undo_idna(lb
, q
, lb
, sizeof(lb
));
450 r
= strcasecmp(la
, lb
);
456 const struct hash_ops dns_name_hash_ops
= {
457 .hash
= dns_name_hash_func
,
458 .compare
= dns_name_compare_func
461 int dns_name_equal(const char *x
, const char *y
) {
468 char la
[DNS_LABEL_MAX
+1], lb
[DNS_LABEL_MAX
+1];
470 if (*x
== 0 && *y
== 0)
473 r
= dns_label_unescape(&x
, la
, sizeof(la
));
477 k
= dns_label_undo_idna(la
, r
, la
, sizeof(la
));
483 q
= dns_label_unescape(&y
, lb
, sizeof(lb
));
486 w
= dns_label_undo_idna(lb
, q
, lb
, sizeof(lb
));
493 if (strcasecmp(la
, lb
))
498 int dns_name_endswith(const char *name
, const char *suffix
) {
499 const char *n
, *s
, *saved_n
= NULL
;
509 char ln
[DNS_LABEL_MAX
+1], ls
[DNS_LABEL_MAX
+1];
511 r
= dns_label_unescape(&n
, ln
, sizeof(ln
));
514 k
= dns_label_undo_idna(ln
, r
, ln
, sizeof(ln
));
523 q
= dns_label_unescape(&s
, ls
, sizeof(ls
));
526 w
= dns_label_undo_idna(ls
, q
, ls
, sizeof(ls
));
532 if (r
== 0 && q
== 0)
534 if (r
== 0 && saved_n
== n
)
539 if (r
!= q
|| strcasecmp(ln
, ls
)) {
541 /* Not the same, let's jump back, and try with the next label again */
549 int dns_name_between(const char *a
, const char *b
, const char *c
) {
552 /* Determine if b is strictly greater than a and strictly smaller than c.
553 We consider the order of names to be circular, so that if a is
554 strictly greater than c, we consider b to be between them if it is
555 either greater than a or smaller than c. This is how the canonical
556 DNS name order used in NSEC records work. */
558 n
= dns_name_compare_func(a
, c
);
563 return dns_name_compare_func(a
, b
) < 0 &&
564 dns_name_compare_func(b
, c
) < 0;
566 /* <--b--c a--b--> */
567 return dns_name_compare_func(b
, c
) < 0 ||
568 dns_name_compare_func(a
, b
) < 0;
571 int dns_name_reverse(int family
, const union in_addr_union
*a
, char **ret
) {
578 p
= (const uint8_t*) a
;
580 if (family
== AF_INET
)
581 r
= asprintf(ret
, "%u.%u.%u.%u.in-addr.arpa", p
[3], p
[2], p
[1], p
[0]);
582 else if (family
== AF_INET6
)
583 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",
584 hexchar(p
[15] & 0xF), hexchar(p
[15] >> 4), hexchar(p
[14] & 0xF), hexchar(p
[14] >> 4),
585 hexchar(p
[13] & 0xF), hexchar(p
[13] >> 4), hexchar(p
[12] & 0xF), hexchar(p
[12] >> 4),
586 hexchar(p
[11] & 0xF), hexchar(p
[11] >> 4), hexchar(p
[10] & 0xF), hexchar(p
[10] >> 4),
587 hexchar(p
[ 9] & 0xF), hexchar(p
[ 9] >> 4), hexchar(p
[ 8] & 0xF), hexchar(p
[ 8] >> 4),
588 hexchar(p
[ 7] & 0xF), hexchar(p
[ 7] >> 4), hexchar(p
[ 6] & 0xF), hexchar(p
[ 6] >> 4),
589 hexchar(p
[ 5] & 0xF), hexchar(p
[ 5] >> 4), hexchar(p
[ 4] & 0xF), hexchar(p
[ 4] >> 4),
590 hexchar(p
[ 3] & 0xF), hexchar(p
[ 3] >> 4), hexchar(p
[ 2] & 0xF), hexchar(p
[ 2] >> 4),
591 hexchar(p
[ 1] & 0xF), hexchar(p
[ 1] >> 4), hexchar(p
[ 0] & 0xF), hexchar(p
[ 0] >> 4));
593 return -EAFNOSUPPORT
;
600 int dns_name_address(const char *p
, int *family
, union in_addr_union
*address
) {
607 r
= dns_name_endswith(p
, "in-addr.arpa");
614 for (i
= 0; i
< ELEMENTSOF(a
); i
++) {
615 char label
[DNS_LABEL_MAX
+1];
617 r
= dns_label_unescape(&p
, label
, sizeof(label
));
625 r
= safe_atou8(label
, &a
[i
]);
630 r
= dns_name_equal(p
, "in-addr.arpa");
635 address
->in
.s_addr
= htobe32(((uint32_t) a
[3] << 24) |
636 ((uint32_t) a
[2] << 16) |
637 ((uint32_t) a
[1] << 8) |
643 r
= dns_name_endswith(p
, "ip6.arpa");
650 for (i
= 0; i
< ELEMENTSOF(a
.s6_addr
); i
++) {
651 char label
[DNS_LABEL_MAX
+1];
654 r
= dns_label_unescape(&p
, label
, sizeof(label
));
659 x
= unhexchar(label
[0]);
663 r
= dns_label_unescape(&p
, label
, sizeof(label
));
668 y
= unhexchar(label
[0]);
672 a
.s6_addr
[ELEMENTSOF(a
.s6_addr
) - i
- 1] = (uint8_t) y
<< 4 | (uint8_t) x
;
675 r
= dns_name_equal(p
, "ip6.arpa");
687 int dns_name_root(const char *name
) {
688 char label
[DNS_LABEL_MAX
+1];
693 r
= dns_label_unescape(&name
, label
, sizeof(label
));
697 return r
== 0 && *name
== 0;
700 int dns_name_single_label(const char *name
) {
701 char label
[DNS_LABEL_MAX
+1];
706 r
= dns_label_unescape(&name
, label
, sizeof(label
));
712 r
= dns_label_unescape(&name
, label
, sizeof(label
));
716 return r
== 0 && *name
== 0;