• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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