1 /* MIT License
2 *
3 * Copyright (c) 2023 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares_setup.h"
27 #include "ares.h"
28 #include "ares_private.h"
29
30 typedef struct {
31 char *name;
32 size_t name_len;
33 size_t idx;
34 } ares_nameoffset_t;
35
ares__nameoffset_free(void * arg)36 static void ares__nameoffset_free(void *arg)
37 {
38 ares_nameoffset_t *off = arg;
39 if (off == NULL) {
40 return;
41 }
42 ares_free(off->name);
43 ares_free(off);
44 }
45
ares__nameoffset_create(ares__llist_t ** list,const char * name,size_t idx)46 static ares_status_t ares__nameoffset_create(ares__llist_t **list,
47 const char *name, size_t idx)
48 {
49 ares_status_t status;
50 ares_nameoffset_t *off = NULL;
51
52 if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
53 ares_strlen(name) > 255) {
54 return ARES_EFORMERR;
55 }
56
57 if (*list == NULL) {
58 *list = ares__llist_create(ares__nameoffset_free);
59 }
60 if (*list == NULL) {
61 status = ARES_ENOMEM;
62 goto fail;
63 }
64
65 off = ares_malloc_zero(sizeof(*off));
66 if (off == NULL) {
67 return ARES_ENOMEM;
68 }
69
70 off->name = ares_strdup(name);
71 off->name_len = ares_strlen(off->name);
72 off->idx = idx;
73
74 if (ares__llist_insert_last(*list, off) == NULL) {
75 status = ARES_ENOMEM;
76 goto fail;
77 }
78
79 return ARES_SUCCESS;
80
81 fail:
82 ares__nameoffset_free(off);
83 return status;
84 }
85
ares__nameoffset_find(ares__llist_t * list,const char * name)86 static const ares_nameoffset_t *ares__nameoffset_find(ares__llist_t *list,
87 const char *name)
88 {
89 size_t name_len = ares_strlen(name);
90 ares__llist_node_t *node;
91 const ares_nameoffset_t *longest_match = NULL;
92
93 if (list == NULL || name == NULL || name_len == 0) {
94 return NULL;
95 }
96
97 for (node = ares__llist_node_first(list); node != NULL;
98 node = ares__llist_node_next(node)) {
99 const ares_nameoffset_t *val = ares__llist_node_val(node);
100 size_t prefix_len;
101
102 /* Can't be a match if the stored name is longer */
103 if (val->name_len > name_len) {
104 continue;
105 }
106
107 /* Can't be the longest match if our existing longest match is longer */
108 if (longest_match != NULL && longest_match->name_len > val->name_len) {
109 continue;
110 }
111
112 prefix_len = name_len - val->name_len;
113
114 if (strcasecmp(val->name, name + prefix_len) != 0) {
115 continue;
116 }
117
118 /* We need to make sure if `val->name` is "example.com" that name is
119 * is separated by a label, e.g. "myexample.com" is not ok, however
120 * "my.example.com" is, so we look for the preceding "." */
121 if (prefix_len != 0 && name[prefix_len - 1] != '.') {
122 continue;
123 }
124
125 longest_match = val;
126 }
127
128 return longest_match;
129 }
130
131 typedef struct {
132 ares__buf_t **label;
133 size_t num;
134 } ares_dns_labels_t;
135
ares_dns_labels_free(ares_dns_labels_t * labels)136 static void ares_dns_labels_free(ares_dns_labels_t *labels)
137 {
138 size_t i;
139
140 if (labels == NULL) {
141 return;
142 }
143
144 for (i = 0; i < labels->num; i++) {
145 ares__buf_destroy(labels->label[i]);
146 labels->label[i] = NULL;
147 }
148 ares_free(labels->label);
149 labels->label = NULL;
150 labels->num = 0;
151 }
152
ares_dns_labels_add(ares_dns_labels_t * labels)153 static ares__buf_t *ares_dns_labels_add(ares_dns_labels_t *labels)
154 {
155 void *temp;
156
157 if (labels == NULL) {
158 return NULL;
159 }
160
161 temp = ares_realloc_zero(labels->label, sizeof(*labels->label) * labels->num,
162 sizeof(*labels->label) * (labels->num + 1));
163 if (temp == NULL) {
164 return NULL;
165 }
166
167 labels->label = temp;
168
169 labels->label[labels->num] = ares__buf_create();
170 if (labels->label[labels->num] == NULL) {
171 return NULL;
172 }
173
174 labels->num++;
175 return labels->label[labels->num - 1];
176 }
177
178 static const ares__buf_t *
ares_dns_labels_get_last(const ares_dns_labels_t * labels)179 ares_dns_labels_get_last(const ares_dns_labels_t *labels)
180 {
181 if (labels == NULL || labels->num == 0) {
182 return NULL;
183 }
184
185 return labels->label[labels->num - 1];
186 }
187
ares_dns_name_labels_del_last(ares_dns_labels_t * labels)188 static void ares_dns_name_labels_del_last(ares_dns_labels_t *labels)
189 {
190 if (labels == NULL || labels->num == 0) {
191 return;
192 }
193
194 ares__buf_destroy(labels->label[labels->num - 1]);
195 labels->label[labels->num - 1] = NULL;
196 labels->num--;
197 }
198
ares_parse_dns_name_escape(ares__buf_t * namebuf,ares__buf_t * label,ares_bool_t validate_hostname)199 static ares_status_t ares_parse_dns_name_escape(ares__buf_t *namebuf,
200 ares__buf_t *label,
201 ares_bool_t validate_hostname)
202 {
203 ares_status_t status;
204 unsigned char c;
205
206 status = ares__buf_fetch_bytes(namebuf, &c, 1);
207 if (status != ARES_SUCCESS) {
208 return ARES_EBADNAME;
209 }
210
211 /* If next character is a digit, read 2 more digits */
212 if (isdigit(c)) {
213 size_t i;
214 unsigned int val = 0;
215
216 val = c - '0';
217
218 for (i = 0; i < 2; i++) {
219 status = ares__buf_fetch_bytes(namebuf, &c, 1);
220 if (status != ARES_SUCCESS) {
221 return ARES_EBADNAME;
222 }
223
224 if (!isdigit(c)) {
225 return ARES_EBADNAME;
226 }
227 val *= 10;
228 val += c - '0';
229 }
230
231 /* Out of range */
232 if (val > 255) {
233 return ARES_EBADNAME;
234 }
235
236 if (validate_hostname && !ares__is_hostnamech((unsigned char)val)) {
237 return ARES_EBADNAME;
238 }
239
240 return ares__buf_append_byte(label, (unsigned char)val);
241 }
242
243 /* We can just output the character */
244 if (validate_hostname && !ares__is_hostnamech(c)) {
245 return ARES_EBADNAME;
246 }
247
248 return ares__buf_append_byte(label, c);
249 }
250
ares_split_dns_name(ares_dns_labels_t * labels,ares_bool_t validate_hostname,const char * name)251 static ares_status_t ares_split_dns_name(ares_dns_labels_t *labels,
252 ares_bool_t validate_hostname,
253 const char *name)
254 {
255 ares_status_t status;
256 ares__buf_t *label = NULL;
257 ares__buf_t *namebuf = NULL;
258 size_t i;
259 size_t total_len = 0;
260 unsigned char c;
261
262 if (name == NULL || labels == NULL) {
263 return ARES_EFORMERR;
264 }
265
266 /* Put name into a buffer for parsing */
267 namebuf = ares__buf_create();
268 if (namebuf == NULL) {
269 status = ARES_ENOMEM;
270 goto done;
271 }
272
273 if (*name != '\0') {
274 status =
275 ares__buf_append(namebuf, (const unsigned char *)name, ares_strlen(name));
276 if (status != ARES_SUCCESS) {
277 goto done;
278 }
279 }
280
281 /* Start with 1 label */
282 label = ares_dns_labels_add(labels);
283 if (label == NULL) {
284 status = ARES_ENOMEM;
285 goto done;
286 }
287
288 while (ares__buf_fetch_bytes(namebuf, &c, 1) == ARES_SUCCESS) {
289 /* New label */
290 if (c == '.') {
291 label = ares_dns_labels_add(labels);
292 if (label == NULL) {
293 status = ARES_ENOMEM;
294 goto done;
295 }
296 continue;
297 }
298
299 /* Escape */
300 if (c == '\\') {
301 status = ares_parse_dns_name_escape(namebuf, label, validate_hostname);
302 if (status != ARES_SUCCESS) {
303 goto done;
304 }
305 continue;
306 }
307
308 /* Output direct character */
309 if (validate_hostname && !ares__is_hostnamech(c)) {
310 status = ARES_EBADNAME;
311 goto done;
312 }
313
314 status = ares__buf_append_byte(label, c);
315 if (status != ARES_SUCCESS) {
316 goto done;
317 }
318 }
319
320 /* Remove trailing blank label */
321 if (ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
322 ares_dns_name_labels_del_last(labels);
323 }
324
325 /* If someone passed in "." there could have been 2 blank labels, check for
326 * that */
327 if (labels->num == 1 &&
328 ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
329 ares_dns_name_labels_del_last(labels);
330 }
331
332 /* Scan to make sure label lengths are valid */
333 for (i = 0; i < labels->num; i++) {
334 size_t len = ares__buf_len(labels->label[i]);
335 /* No 0-length labels, and no labels over 63 bytes */
336 if (len == 0 || len > 63) {
337 status = ARES_EBADNAME;
338 goto done;
339 }
340 total_len += len;
341 }
342
343 /* Can't exceed maximum (unescaped) length */
344 if (labels->num && total_len + labels->num - 1 > 255) {
345 status = ARES_EBADNAME;
346 goto done;
347 }
348
349 status = ARES_SUCCESS;
350
351 done:
352 ares__buf_destroy(namebuf);
353 if (status != ARES_SUCCESS) {
354 ares_dns_labels_free(labels);
355 }
356 return status;
357 }
358
ares__dns_name_write(ares__buf_t * buf,ares__llist_t ** list,ares_bool_t validate_hostname,const char * name)359 ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list,
360 ares_bool_t validate_hostname,
361 const char *name)
362 {
363 const ares_nameoffset_t *off = NULL;
364 size_t name_len;
365 size_t pos = ares__buf_len(buf);
366 ares_dns_labels_t labels;
367 char name_copy[512];
368 ares_status_t status;
369
370 if (buf == NULL || name == NULL) {
371 return ARES_EFORMERR;
372 }
373
374 memset(&labels, 0, sizeof(labels));
375
376 /* NOTE: due to possible escaping, name_copy buffer is > 256 to allow for
377 * this */
378 name_len = ares_strcpy(name_copy, name, sizeof(name_copy));
379
380 /* Find longest match */
381 if (list != NULL) {
382 off = ares__nameoffset_find(*list, name_copy);
383 if (off != NULL && off->name_len != name_len) {
384 /* truncate */
385 name_len -= (off->name_len + 1);
386 name_copy[name_len] = 0;
387 }
388 }
389
390 /* Output labels */
391 if (off == NULL || off->name_len != name_len) {
392 size_t i;
393
394 status = ares_split_dns_name(&labels, validate_hostname, name_copy);
395 if (status != ARES_SUCCESS) {
396 goto done;
397 }
398
399 for (i = 0; i < labels.num; i++) {
400 size_t len = 0;
401 const unsigned char *ptr = ares__buf_peek(labels.label[i], &len);
402
403 status = ares__buf_append_byte(buf, (unsigned char)(len & 0xFF));
404 if (status != ARES_SUCCESS) {
405 goto done;
406 }
407
408 status = ares__buf_append(buf, ptr, len);
409 if (status != ARES_SUCCESS) {
410 goto done;
411 }
412 }
413
414 /* If we are NOT jumping to another label, output terminator */
415 if (off == NULL) {
416 status = ares__buf_append_byte(buf, 0);
417 if (status != ARES_SUCCESS) {
418 goto done;
419 }
420 }
421 }
422
423 /* Output name compression offset jump */
424 if (off != NULL) {
425 unsigned short u16 =
426 (unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
427 status = ares__buf_append_be16(buf, u16);
428 if (status != ARES_SUCCESS) {
429 goto done;
430 }
431 }
432
433 /* Store pointer for future jumps as long as its not an exact match for
434 * a prior entry */
435 if (list != NULL && (off == NULL || off->name_len != name_len) &&
436 name_len > 0) {
437 status = ares__nameoffset_create(list, name /* not truncated copy! */, pos);
438 if (status != ARES_SUCCESS) {
439 goto done;
440 }
441 }
442
443 status = ARES_SUCCESS;
444
445 done:
446 ares_dns_labels_free(&labels);
447 return status;
448 }
449
450 /* Reserved characters for names that need to be escaped */
is_reservedch(int ch)451 static ares_bool_t is_reservedch(int ch)
452 {
453 switch (ch) {
454 case '"':
455 case '.':
456 case ';':
457 case '\\':
458 case '(':
459 case ')':
460 case '@':
461 case '$':
462 return ARES_TRUE;
463 default:
464 break;
465 }
466
467 return ARES_FALSE;
468 }
469
ares__fetch_dnsname_into_buf(ares__buf_t * buf,ares__buf_t * dest,size_t len,ares_bool_t is_hostname)470 static ares_status_t ares__fetch_dnsname_into_buf(ares__buf_t *buf,
471 ares__buf_t *dest, size_t len,
472 ares_bool_t is_hostname)
473 {
474 size_t remaining_len;
475 const unsigned char *ptr = ares__buf_peek(buf, &remaining_len);
476 ares_status_t status;
477 size_t i;
478
479 if (buf == NULL || len == 0 || remaining_len < len) {
480 return ARES_EBADRESP;
481 }
482
483 for (i = 0; i < len; i++) {
484 unsigned char c = ptr[i];
485
486 /* Hostnames have a very specific allowed character set. Anything outside
487 * of that (non-printable and reserved included) are disallowed */
488 if (is_hostname && !ares__is_hostnamech(c)) {
489 status = ARES_EBADRESP;
490 goto fail;
491 }
492
493 /* NOTE: dest may be NULL if the user is trying to skip the name. validation
494 * still occurs above. */
495 if (dest == NULL) {
496 continue;
497 }
498
499 /* Non-printable characters need to be output as \DDD */
500 if (!ares__isprint(c)) {
501 unsigned char escape[4];
502
503 escape[0] = '\\';
504 escape[1] = '0' + (c / 100);
505 escape[2] = '0' + ((c % 100) / 10);
506 escape[3] = '0' + (c % 10);
507
508 status = ares__buf_append(dest, escape, sizeof(escape));
509 if (status != ARES_SUCCESS) {
510 goto fail;
511 }
512
513 continue;
514 }
515
516 /* Reserved characters need to be escaped, otherwise normal */
517 if (is_reservedch(c)) {
518 status = ares__buf_append_byte(dest, '\\');
519 if (status != ARES_SUCCESS) {
520 goto fail;
521 }
522 }
523
524 status = ares__buf_append_byte(dest, c);
525 if (status != ARES_SUCCESS) {
526 return status;
527 }
528 }
529
530 return ares__buf_consume(buf, len);
531
532 fail:
533 return status;
534 }
535
ares__dns_name_parse(ares__buf_t * buf,char ** name,ares_bool_t is_hostname)536 ares_status_t ares__dns_name_parse(ares__buf_t *buf, char **name,
537 ares_bool_t is_hostname)
538 {
539 size_t save_offset = 0;
540 unsigned char c;
541 ares_status_t status;
542 ares__buf_t *namebuf = NULL;
543 size_t label_start = ares__buf_get_position(buf);
544
545 if (buf == NULL) {
546 return ARES_EFORMERR;
547 }
548
549 if (name != NULL) {
550 namebuf = ares__buf_create();
551 if (namebuf == NULL) {
552 status = ARES_ENOMEM;
553 goto fail;
554 }
555 }
556
557 /* The compression scheme allows a domain name in a message to be
558 * represented as either:
559 *
560 * - a sequence of labels ending in a zero octet
561 * - a pointer
562 * - a sequence of labels ending with a pointer
563 */
564 while (1) {
565 /* Keep track of the minimum label starting position to prevent forward
566 * jumping */
567 if (label_start > ares__buf_get_position(buf)) {
568 label_start = ares__buf_get_position(buf);
569 }
570
571 status = ares__buf_fetch_bytes(buf, &c, 1);
572 if (status != ARES_SUCCESS) {
573 goto fail;
574 }
575
576 /* Pointer/Redirect */
577 if ((c & 0xc0) == 0xc0) {
578 /* The pointer takes the form of a two octet sequence:
579 *
580 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
581 * | 1 1| OFFSET |
582 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
583 *
584 * The first two bits are ones. This allows a pointer to be distinguished
585 * from a label, since the label must begin with two zero bits because
586 * labels are restricted to 63 octets or less. (The 10 and 01
587 * combinations are reserved for future use.) The OFFSET field specifies
588 * an offset from the start of the message (i.e., the first octet of the
589 * ID field in the domain header). A zero offset specifies the first byte
590 * of the ID field, etc.
591 */
592 size_t offset = (size_t)((c & 0x3F) << 8);
593
594 /* Fetch second byte of the redirect length */
595 status = ares__buf_fetch_bytes(buf, &c, 1);
596 if (status != ARES_SUCCESS) {
597 goto fail;
598 }
599
600 offset |= ((size_t)c);
601
602 /* According to RFC 1035 4.1.4:
603 * In this scheme, an entire domain name or a list of labels at
604 * the end of a domain name is replaced with a pointer to a prior
605 * occurrence of the same name.
606 * Note the word "prior", meaning it must go backwards. This was
607 * confirmed via the ISC BIND code that it also prevents forward
608 * pointers.
609 */
610 if (offset >= label_start) {
611 status = ARES_EBADNAME;
612 goto fail;
613 }
614
615 /* First time we make a jump, save the current position */
616 if (save_offset == 0) {
617 save_offset = ares__buf_get_position(buf);
618 }
619
620 status = ares__buf_set_position(buf, offset);
621 if (status != ARES_SUCCESS) {
622 status = ARES_EBADNAME;
623 goto fail;
624 }
625
626 continue;
627 } else if ((c & 0xc0) != 0) {
628 /* 10 and 01 are reserved */
629 status = ARES_EBADNAME;
630 goto fail;
631 } else if (c == 0) {
632 /* termination via zero octet*/
633 break;
634 }
635
636 /* New label */
637
638 /* Labels are separated by periods */
639 if (ares__buf_len(namebuf) != 0 && name != NULL) {
640 status = ares__buf_append_byte(namebuf, '.');
641 if (status != ARES_SUCCESS) {
642 goto fail;
643 }
644 }
645
646 status = ares__fetch_dnsname_into_buf(buf, namebuf, c, is_hostname);
647 if (status != ARES_SUCCESS) {
648 goto fail;
649 }
650 }
651
652 /* Restore offset read after first redirect/pointer as this is where the DNS
653 * message continues */
654 if (save_offset) {
655 ares__buf_set_position(buf, save_offset);
656 }
657
658 if (name != NULL) {
659 *name = ares__buf_finish_str(namebuf, NULL);
660 if (*name == NULL) {
661 status = ARES_ENOMEM;
662 goto fail;
663 }
664 }
665
666 return ARES_SUCCESS;
667
668 fail:
669 /* We want badname response if we couldn't parse */
670 if (status == ARES_EBADRESP) {
671 status = ARES_EBADNAME;
672 }
673
674 ares__buf_destroy(namebuf);
675 return status;
676 }
677