quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

ares_dns_name.c (18545B)


      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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    187 static void ares_dns_name_labels_del_last(ares_array_t *labels)
    188 {
    189   ares_array_remove_last(labels);
    190 }
    191 
    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 
    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 
    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 */
    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 
    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 
    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 }