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
30typedef struct {
31  char  *name;
32  size_t name_len;
33  size_t idx;
34} ares_nameoffset_t;
35
36static 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
46static 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
81fail:
82  ares__nameoffset_free(off);
83  return status;
84}
85
86static 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
131typedef struct {
132  ares__buf_t **label;
133  size_t        num;
134} ares_dns_labels_t;
135
136static 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
153static 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
178static const ares__buf_t *
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
188static 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
199static 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
251static 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
351done:
352  ares__buf_destroy(namebuf);
353  if (status != ARES_SUCCESS) {
354    ares_dns_labels_free(labels);
355  }
356  return status;
357}
358
359ares_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
445done:
446  ares_dns_labels_free(&labels);
447  return status;
448}
449
450/* Reserved characters for names that need to be escaped */
451static 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
470static 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
532fail:
533  return status;
534}
535
536ares_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
668fail:
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