• 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_private.h"
27 
28 typedef struct {
29   char  *name;
30   size_t name_len;
31   size_t idx;
32 } ares_nameoffset_t;
33 
ares_nameoffset_free(void * arg)34 static void ares_nameoffset_free(void *arg)
35 {
36   ares_nameoffset_t *off = arg;
37   if (off == NULL) {
38     return; /* LCOV_EXCL_LINE: DefensiveCoding */
39   }
40   ares_free(off->name);
41   ares_free(off);
42 }
43 
ares_nameoffset_create(ares_llist_t ** list,const char * name,size_t idx)44 static ares_status_t ares_nameoffset_create(ares_llist_t **list,
45                                             const char *name, size_t idx)
46 {
47   ares_status_t      status;
48   ares_nameoffset_t *off = NULL;
49 
50   if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
51       ares_strlen(name) > 255) {
52     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
53   }
54 
55   if (*list == NULL) {
56     *list = ares_llist_create(ares_nameoffset_free);
57   }
58   if (*list == NULL) {
59     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
60     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
61   }
62 
63   off = ares_malloc_zero(sizeof(*off));
64   if (off == NULL) {
65     return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
66   }
67 
68   off->name     = ares_strdup(name);
69   off->name_len = ares_strlen(off->name);
70   off->idx      = idx;
71 
72   if (ares_llist_insert_last(*list, off) == NULL) {
73     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
74     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
75   }
76 
77   return ARES_SUCCESS;
78 
79 /* LCOV_EXCL_START: OutOfMemory */
80 fail:
81   ares_nameoffset_free(off);
82   return status;
83   /* LCOV_EXCL_STOP */
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     /* Due to DNS 0x20, lets not inadvertently mangle things, use case-sensitive
115      * matching instead of case-insensitive.  This may result in slightly
116      * larger DNS queries overall. */
117     if (!ares_streq(val->name, name + prefix_len)) {
118       continue;
119     }
120 
121     /* We need to make sure if `val->name` is "example.com" that name is
122      * is separated by a label, e.g. "myexample.com" is not ok, however
123      * "my.example.com" is, so we look for the preceding "." */
124     if (prefix_len != 0 && name[prefix_len - 1] != '.') {
125       continue;
126     }
127 
128     longest_match = val;
129   }
130 
131   return longest_match;
132 }
133 
ares_dns_labels_free_cb(void * arg)134 static void ares_dns_labels_free_cb(void *arg)
135 {
136   ares_buf_t **buf = arg;
137   if (buf == NULL) {
138     return;
139   }
140 
141   ares_buf_destroy(*buf);
142 }
143 
ares_dns_labels_add(ares_array_t * labels)144 static ares_buf_t *ares_dns_labels_add(ares_array_t *labels)
145 {
146   ares_buf_t **buf;
147 
148   if (labels == NULL) {
149     return NULL; /* LCOV_EXCL_LINE: DefensiveCoding */
150   }
151 
152   if (ares_array_insert_last((void **)&buf, labels) != ARES_SUCCESS) {
153     return NULL;
154   }
155 
156   *buf = ares_buf_create();
157   if (*buf == NULL) {
158     ares_array_remove_last(labels);
159     return NULL;
160   }
161 
162   return *buf;
163 }
164 
ares_dns_labels_get_last(ares_array_t * labels)165 static ares_buf_t *ares_dns_labels_get_last(ares_array_t *labels)
166 {
167   ares_buf_t **buf = ares_array_last(labels);
168 
169   if (buf == NULL) {
170     return NULL;
171   }
172 
173   return *buf;
174 }
175 
ares_dns_labels_get_at(ares_array_t * labels,size_t idx)176 static ares_buf_t *ares_dns_labels_get_at(ares_array_t *labels, size_t idx)
177 {
178   ares_buf_t **buf = ares_array_at(labels, idx);
179 
180   if (buf == NULL) {
181     return NULL;
182   }
183 
184   return *buf;
185 }
186 
ares_dns_name_labels_del_last(ares_array_t * labels)187 static void ares_dns_name_labels_del_last(ares_array_t *labels)
188 {
189   ares_array_remove_last(labels);
190 }
191 
ares_parse_dns_name_escape(ares_buf_t * namebuf,ares_buf_t * label,ares_bool_t validate_hostname)192 static ares_status_t ares_parse_dns_name_escape(ares_buf_t *namebuf,
193                                                 ares_buf_t *label,
194                                                 ares_bool_t validate_hostname)
195 {
196   ares_status_t status;
197   unsigned char c;
198 
199   status = ares_buf_fetch_bytes(namebuf, &c, 1);
200   if (status != ARES_SUCCESS) {
201     return ARES_EBADNAME;
202   }
203 
204   /* If next character is a digit, read 2 more digits */
205   if (ares_isdigit(c)) {
206     size_t       i;
207     unsigned int val = 0;
208 
209     val = c - '0';
210 
211     for (i = 0; i < 2; i++) {
212       status = ares_buf_fetch_bytes(namebuf, &c, 1);
213       if (status != ARES_SUCCESS) {
214         return ARES_EBADNAME;
215       }
216 
217       if (!ares_isdigit(c)) {
218         return ARES_EBADNAME;
219       }
220       val *= 10;
221       val += c - '0';
222     }
223 
224     /* Out of range */
225     if (val > 255) {
226       return ARES_EBADNAME;
227     }
228 
229     if (validate_hostname && !ares_is_hostnamech((unsigned char)val)) {
230       return ARES_EBADNAME;
231     }
232 
233     return ares_buf_append_byte(label, (unsigned char)val);
234   }
235 
236   /* We can just output the character */
237   if (validate_hostname && !ares_is_hostnamech(c)) {
238     return ARES_EBADNAME;
239   }
240 
241   return ares_buf_append_byte(label, c);
242 }
243 
ares_split_dns_name(ares_array_t * labels,ares_bool_t validate_hostname,const char * name)244 static ares_status_t ares_split_dns_name(ares_array_t *labels,
245                                          ares_bool_t   validate_hostname,
246                                          const char   *name)
247 {
248   ares_status_t status;
249   ares_buf_t   *label   = NULL;
250   ares_buf_t   *namebuf = NULL;
251   size_t        i;
252   size_t        total_len = 0;
253   unsigned char c;
254 
255   if (name == NULL || labels == NULL) {
256     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
257   }
258 
259   /* Put name into a buffer for parsing */
260   namebuf = ares_buf_create();
261   if (namebuf == NULL) {
262     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
263     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
264   }
265 
266   if (*name != '\0') {
267     status =
268       ares_buf_append(namebuf, (const unsigned char *)name, ares_strlen(name));
269     if (status != ARES_SUCCESS) {
270       goto done; /* LCOV_EXCL_LINE: OutOfMemory */
271     }
272   }
273 
274   /* Start with 1 label */
275   label = ares_dns_labels_add(labels);
276   if (label == NULL) {
277     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
278     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
279   }
280 
281   while (ares_buf_fetch_bytes(namebuf, &c, 1) == ARES_SUCCESS) {
282     /* New label */
283     if (c == '.') {
284       label = ares_dns_labels_add(labels);
285       if (label == NULL) {
286         status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
287         goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
288       }
289       continue;
290     }
291 
292     /* Escape */
293     if (c == '\\') {
294       status = ares_parse_dns_name_escape(namebuf, label, validate_hostname);
295       if (status != ARES_SUCCESS) {
296         goto done;
297       }
298       continue;
299     }
300 
301     /* Output direct character */
302     if (validate_hostname && !ares_is_hostnamech(c)) {
303       status = ARES_EBADNAME;
304       goto done;
305     }
306 
307     status = ares_buf_append_byte(label, c);
308     if (status != ARES_SUCCESS) {
309       goto done; /* LCOV_EXCL_LINE: OutOfMemory */
310     }
311   }
312 
313   /* Remove trailing blank label */
314   if (ares_buf_len(ares_dns_labels_get_last(labels)) == 0) {
315     ares_dns_name_labels_del_last(labels);
316   }
317 
318   /* If someone passed in "." there could have been 2 blank labels, check for
319    * that */
320   if (ares_array_len(labels) == 1 &&
321       ares_buf_len(ares_dns_labels_get_last(labels)) == 0) {
322     ares_dns_name_labels_del_last(labels);
323   }
324 
325   /* Scan to make sure label lengths are valid */
326   for (i = 0; i < ares_array_len(labels); i++) {
327     const ares_buf_t *buf = ares_dns_labels_get_at(labels, i);
328     size_t            len = ares_buf_len(buf);
329     /* No 0-length labels, and no labels over 63 bytes */
330     if (len == 0 || len > 63) {
331       status = ARES_EBADNAME;
332       goto done;
333     }
334     total_len += len;
335   }
336 
337   /* Can't exceed maximum (unescaped) length */
338   if (ares_array_len(labels) && total_len + ares_array_len(labels) - 1 > 255) {
339     status = ARES_EBADNAME;
340     goto done;
341   }
342 
343   status = ARES_SUCCESS;
344 
345 done:
346   ares_buf_destroy(namebuf);
347   return status;
348 }
349 
ares_dns_name_write(ares_buf_t * buf,ares_llist_t ** list,ares_bool_t validate_hostname,const char * name)350 ares_status_t ares_dns_name_write(ares_buf_t *buf, ares_llist_t **list,
351                                   ares_bool_t validate_hostname,
352                                   const char *name)
353 {
354   const ares_nameoffset_t *off = NULL;
355   size_t                   name_len;
356   size_t                   orig_name_len;
357   size_t                   pos    = ares_buf_len(buf);
358   ares_array_t            *labels = NULL;
359   char                     name_copy[512];
360   ares_status_t            status;
361 
362   if (buf == NULL || name == NULL) {
363     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
364   }
365 
366   labels = ares_array_create(sizeof(ares_buf_t *), ares_dns_labels_free_cb);
367   if (labels == NULL) {
368     return ARES_ENOMEM;
369   }
370 
371   /* NOTE: due to possible escaping, name_copy buffer is > 256 to allow for
372    *       this */
373   name_len      = ares_strcpy(name_copy, name, sizeof(name_copy));
374   orig_name_len = name_len;
375 
376   /* Find longest match */
377   if (list != NULL) {
378     off = ares_nameoffset_find(*list, name_copy);
379     if (off != NULL && off->name_len != name_len) {
380       /* truncate */
381       name_len            -= (off->name_len + 1);
382       name_copy[name_len]  = 0;
383     }
384   }
385 
386   /* Output labels */
387   if (off == NULL || off->name_len != orig_name_len) {
388     size_t i;
389 
390     status = ares_split_dns_name(labels, validate_hostname, name_copy);
391     if (status != ARES_SUCCESS) {
392       goto done;
393     }
394 
395     for (i = 0; i < ares_array_len(labels); i++) {
396       size_t               len  = 0;
397       const ares_buf_t    *lbuf = ares_dns_labels_get_at(labels, i);
398       const unsigned char *ptr  = ares_buf_peek(lbuf, &len);
399 
400       status = ares_buf_append_byte(buf, (unsigned char)(len & 0xFF));
401       if (status != ARES_SUCCESS) {
402         goto done; /* LCOV_EXCL_LINE: OutOfMemory */
403       }
404 
405       status = ares_buf_append(buf, ptr, len);
406       if (status != ARES_SUCCESS) {
407         goto done; /* LCOV_EXCL_LINE: OutOfMemory */
408       }
409     }
410 
411     /* If we are NOT jumping to another label, output terminator */
412     if (off == NULL) {
413       status = ares_buf_append_byte(buf, 0);
414       if (status != ARES_SUCCESS) {
415         goto done; /* LCOV_EXCL_LINE: OutOfMemory */
416       }
417     }
418   }
419 
420   /* Output name compression offset jump */
421   if (off != NULL) {
422     unsigned short u16 =
423       (unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
424     status = ares_buf_append_be16(buf, u16);
425     if (status != ARES_SUCCESS) {
426       goto done; /* LCOV_EXCL_LINE: OutOfMemory */
427     }
428   }
429 
430   /* Store pointer for future jumps as long as its not an exact match for
431    * a prior entry */
432   if (list != NULL && (off == NULL || off->name_len != orig_name_len) &&
433       name_len > 0) {
434     status = ares_nameoffset_create(list, name /* not truncated copy! */, pos);
435     if (status != ARES_SUCCESS) {
436       goto done; /* LCOV_EXCL_LINE: OutOfMemory */
437     }
438   }
439 
440   status = ARES_SUCCESS;
441 
442 done:
443   ares_array_destroy(labels);
444   return status;
445 }
446 
447 /* Reserved characters for names that need to be escaped */
is_reservedch(int ch)448 static ares_bool_t is_reservedch(int ch)
449 {
450   switch (ch) {
451     case '"':
452     case '.':
453     case ';':
454     case '\\':
455     case '(':
456     case ')':
457     case '@':
458     case '$':
459       return ARES_TRUE;
460     default:
461       break;
462   }
463 
464   return ARES_FALSE;
465 }
466 
ares_fetch_dnsname_into_buf(ares_buf_t * buf,ares_buf_t * dest,size_t len,ares_bool_t is_hostname)467 static ares_status_t ares_fetch_dnsname_into_buf(ares_buf_t *buf,
468                                                  ares_buf_t *dest, size_t len,
469                                                  ares_bool_t is_hostname)
470 {
471   size_t               remaining_len;
472   const unsigned char *ptr = ares_buf_peek(buf, &remaining_len);
473   ares_status_t        status;
474   size_t               i;
475 
476   if (buf == NULL || len == 0 || remaining_len < len) {
477     return ARES_EBADRESP;
478   }
479 
480   for (i = 0; i < len; i++) {
481     unsigned char c = ptr[i];
482 
483     /* Hostnames have a very specific allowed character set.  Anything outside
484      * of that (non-printable and reserved included) are disallowed */
485     if (is_hostname && !ares_is_hostnamech(c)) {
486       status = ARES_EBADRESP;
487       goto fail;
488     }
489 
490     /* NOTE: dest may be NULL if the user is trying to skip the name. validation
491      *       still occurs above. */
492     if (dest == NULL) {
493       continue;
494     }
495 
496     /* Non-printable characters need to be output as \DDD */
497     if (!ares_isprint(c)) {
498       unsigned char escape[4];
499 
500       escape[0] = '\\';
501       escape[1] = '0' + (c / 100);
502       escape[2] = '0' + ((c % 100) / 10);
503       escape[3] = '0' + (c % 10);
504 
505       status = ares_buf_append(dest, escape, sizeof(escape));
506       if (status != ARES_SUCCESS) {
507         goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
508       }
509 
510       continue;
511     }
512 
513     /* Reserved characters need to be escaped, otherwise normal */
514     if (is_reservedch(c)) {
515       status = ares_buf_append_byte(dest, '\\');
516       if (status != ARES_SUCCESS) {
517         goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
518       }
519     }
520 
521     status = ares_buf_append_byte(dest, c);
522     if (status != ARES_SUCCESS) {
523       return status; /* LCOV_EXCL_LINE: OutOfMemory */
524     }
525   }
526 
527   return ares_buf_consume(buf, len);
528 
529 fail:
530   return status;
531 }
532 
ares_dns_name_parse(ares_buf_t * buf,char ** name,ares_bool_t is_hostname)533 ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
534                                   ares_bool_t is_hostname)
535 {
536   size_t        save_offset = 0;
537   unsigned char c;
538   ares_status_t status;
539   ares_buf_t   *namebuf     = NULL;
540   size_t        label_start = ares_buf_get_position(buf);
541 
542   if (buf == NULL) {
543     return ARES_EFORMERR;
544   }
545 
546   if (name != NULL) {
547     namebuf = ares_buf_create();
548     if (namebuf == NULL) {
549       status = ARES_ENOMEM;
550       goto fail;
551     }
552   }
553 
554   /* The compression scheme allows a domain name in a message to be
555    * represented as either:
556    *
557    * - a sequence of labels ending in a zero octet
558    * - a pointer
559    * - a sequence of labels ending with a pointer
560    */
561   while (1) {
562     /* Keep track of the minimum label starting position to prevent forward
563      * jumping */
564     if (label_start > ares_buf_get_position(buf)) {
565       label_start = ares_buf_get_position(buf);
566     }
567 
568     status = ares_buf_fetch_bytes(buf, &c, 1);
569     if (status != ARES_SUCCESS) {
570       goto fail;
571     }
572 
573     /* Pointer/Redirect */
574     if ((c & 0xc0) == 0xc0) {
575       /* The pointer takes the form of a two octet sequence:
576        *
577        *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
578        *   | 1  1|                OFFSET                   |
579        *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
580        *
581        * The first two bits are ones.  This allows a pointer to be distinguished
582        * from a label, since the label must begin with two zero bits because
583        * labels are restricted to 63 octets or less.  (The 10 and 01
584        * combinations are reserved for future use.)  The OFFSET field specifies
585        * an offset from the start of the message (i.e., the first octet of the
586        * ID field in the domain header).  A zero offset specifies the first byte
587        * of the ID field, etc.
588        */
589       size_t offset = (size_t)((c & 0x3F) << 8);
590 
591       /* Fetch second byte of the redirect length */
592       status = ares_buf_fetch_bytes(buf, &c, 1);
593       if (status != ARES_SUCCESS) {
594         goto fail;
595       }
596 
597       offset |= ((size_t)c);
598 
599       /* According to RFC 1035 4.1.4:
600        *    In this scheme, an entire domain name or a list of labels at
601        *    the end of a domain name is replaced with a pointer to a prior
602        *    occurrence of the same name.
603        * Note the word "prior", meaning it must go backwards.  This was
604        * confirmed via the ISC BIND code that it also prevents forward
605        * pointers.
606        */
607       if (offset >= label_start) {
608         status = ARES_EBADNAME;
609         goto fail;
610       }
611 
612       /* First time we make a jump, save the current position */
613       if (save_offset == 0) {
614         save_offset = ares_buf_get_position(buf);
615       }
616 
617       status = ares_buf_set_position(buf, offset);
618       if (status != ARES_SUCCESS) {
619         status = ARES_EBADNAME;
620         goto fail;
621       }
622 
623       continue;
624     } else if ((c & 0xc0) != 0) {
625       /* 10 and 01 are reserved */
626       status = ARES_EBADNAME;
627       goto fail;
628     } else if (c == 0) {
629       /* termination via zero octet*/
630       break;
631     }
632 
633     /* New label */
634 
635     /* Labels are separated by periods */
636     if (ares_buf_len(namebuf) != 0 && name != NULL) {
637       status = ares_buf_append_byte(namebuf, '.');
638       if (status != ARES_SUCCESS) {
639         goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
640       }
641     }
642 
643     status = ares_fetch_dnsname_into_buf(buf, namebuf, c, is_hostname);
644     if (status != ARES_SUCCESS) {
645       goto fail;
646     }
647   }
648 
649   /* Restore offset read after first redirect/pointer as this is where the DNS
650    * message continues */
651   if (save_offset) {
652     ares_buf_set_position(buf, save_offset);
653   }
654 
655   if (name != NULL) {
656     *name = ares_buf_finish_str(namebuf, NULL);
657     if (*name == NULL) {
658       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
659       goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
660     }
661   }
662 
663   return ARES_SUCCESS;
664 
665 fail:
666   /* We want badname response if we couldn't parse */
667   if (status == ARES_EBADRESP) {
668     status = ARES_EBADNAME;
669   }
670 
671   ares_buf_destroy(namebuf);
672   return status;
673 }
674