quickjs-tart

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

ares_search.c (17895B)


      1 /* MIT License
      2  *
      3  * Copyright (c) 1998 Massachusetts Institute of Technology
      4  * Copyright (c) The c-ares project and its contributors
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a copy
      7  * of this software and associated documentation files (the "Software"), to deal
      8  * in the Software without restriction, including without limitation the rights
      9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10  * copies of the Software, and to permit persons to whom the Software is
     11  * furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice (including the next
     14  * paragraph) shall be included in all copies or substantial portions of the
     15  * Software.
     16  *
     17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     23  * SOFTWARE.
     24  *
     25  * SPDX-License-Identifier: MIT
     26  */
     27 
     28 #include "ares_private.h"
     29 
     30 #ifdef HAVE_STRINGS_H
     31 #  include <strings.h>
     32 #endif
     33 
     34 struct search_query {
     35   /* Arguments passed to ares_search_dnsrec() */
     36   ares_channel_t      *channel;
     37   ares_callback_dnsrec callback;
     38   void                *arg;
     39 
     40   /* Duplicate of DNS record passed to ares_search_dnsrec() */
     41   ares_dns_record_t   *dnsrec;
     42 
     43   /* Search order for names */
     44   char               **names;
     45   size_t               names_cnt;
     46 
     47   /* State tracking progress through the search query */
     48   size_t               next_name_idx; /* next name index being attempted */
     49   size_t      timeouts;        /* number of timeouts we saw for this request */
     50   ares_bool_t ever_got_nodata; /* did we ever get ARES_ENODATA along the way? */
     51 };
     52 
     53 static void squery_free(struct search_query *squery)
     54 {
     55   if (squery == NULL) {
     56     return; /* LCOV_EXCL_LINE: DefensiveCoding */
     57   }
     58   ares_strsplit_free(squery->names, squery->names_cnt);
     59   ares_dns_record_destroy(squery->dnsrec);
     60   ares_free(squery);
     61 }
     62 
     63 /* End a search query by invoking the user callback and freeing the
     64  * search_query structure.
     65  */
     66 static void end_squery(struct search_query *squery, ares_status_t status,
     67                        const ares_dns_record_t *dnsrec)
     68 {
     69   squery->callback(squery->arg, status, squery->timeouts, dnsrec);
     70   squery_free(squery);
     71 }
     72 
     73 static void search_callback(void *arg, ares_status_t status, size_t timeouts,
     74                             const ares_dns_record_t *dnsrec);
     75 
     76 static ares_status_t ares_search_next(ares_channel_t      *channel,
     77                                       struct search_query *squery,
     78                                       ares_bool_t         *skip_cleanup)
     79 {
     80   ares_status_t status;
     81 
     82   *skip_cleanup = ARES_FALSE;
     83 
     84   /* Misuse check */
     85   if (squery->next_name_idx >= squery->names_cnt) {
     86     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
     87   }
     88 
     89   status = ares_dns_record_query_set_name(
     90     squery->dnsrec, 0, squery->names[squery->next_name_idx++]);
     91   if (status != ARES_SUCCESS) {
     92     return status;
     93   }
     94 
     95   status = ares_send_nolock(channel, NULL, 0, squery->dnsrec, search_callback,
     96                             squery, NULL);
     97 
     98   if (status != ARES_EFORMERR) {
     99     *skip_cleanup = ARES_TRUE;
    100   }
    101 
    102   return status;
    103 }
    104 
    105 static void search_callback(void *arg, ares_status_t status, size_t timeouts,
    106                             const ares_dns_record_t *dnsrec)
    107 {
    108   struct search_query *squery  = (struct search_query *)arg;
    109   ares_channel_t      *channel = squery->channel;
    110 
    111   ares_status_t        mystatus;
    112   ares_bool_t          skip_cleanup = ARES_FALSE;
    113 
    114   squery->timeouts += timeouts;
    115 
    116   if (dnsrec) {
    117     ares_dns_rcode_t rcode = ares_dns_record_get_rcode(dnsrec);
    118     size_t ancount = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER);
    119     mystatus       = ares_dns_query_reply_tostatus(rcode, ancount);
    120   } else {
    121     mystatus = status;
    122   }
    123 
    124   switch (mystatus) {
    125     case ARES_ENODATA:
    126     case ARES_ENOTFOUND:
    127       break;
    128     case ARES_ESERVFAIL:
    129     case ARES_EREFUSED:
    130       /* Issue #852, systemd-resolved may return SERVFAIL or REFUSED on a
    131        * single label domain name. */
    132       if (ares_name_label_cnt(squery->names[squery->next_name_idx - 1]) != 1) {
    133         end_squery(squery, mystatus, dnsrec);
    134         return;
    135       }
    136       break;
    137     default:
    138       end_squery(squery, mystatus, dnsrec);
    139       return;
    140   }
    141 
    142   /* If we ever get ARES_ENODATA along the way, record that; if the search
    143    * should run to the very end and we got at least one ARES_ENODATA,
    144    * then callers like ares_gethostbyname() may want to try a T_A search
    145    * even if the last domain we queried for T_AAAA resource records
    146    * returned ARES_ENOTFOUND.
    147    */
    148   if (mystatus == ARES_ENODATA) {
    149     squery->ever_got_nodata = ARES_TRUE;
    150   }
    151 
    152   if (squery->next_name_idx < squery->names_cnt) {
    153     mystatus = ares_search_next(channel, squery, &skip_cleanup);
    154     if (mystatus != ARES_SUCCESS && !skip_cleanup) {
    155       end_squery(squery, mystatus, NULL);
    156     }
    157     return;
    158   }
    159 
    160   /* We have no more domains to search, return an appropriate response. */
    161   if (mystatus == ARES_ENOTFOUND && squery->ever_got_nodata) {
    162     end_squery(squery, ARES_ENODATA, NULL);
    163     return;
    164   }
    165 
    166   end_squery(squery, mystatus, NULL);
    167 }
    168 
    169 /* Determine if the domain should be looked up as-is, or if it is eligible
    170  * for search by appending domains */
    171 static ares_bool_t ares_search_eligible(const ares_channel_t *channel,
    172                                         const char           *name)
    173 {
    174   size_t len = ares_strlen(name);
    175 
    176   /* Name ends in '.', cannot search */
    177   if (len && name[len - 1] == '.') {
    178     return ARES_FALSE;
    179   }
    180 
    181   if (channel->flags & ARES_FLAG_NOSEARCH) {
    182     return ARES_FALSE;
    183   }
    184 
    185   return ARES_TRUE;
    186 }
    187 
    188 size_t ares_name_label_cnt(const char *name)
    189 {
    190   const char *p;
    191   size_t      ndots = 0;
    192 
    193   if (name == NULL) {
    194     return 0;
    195   }
    196 
    197   for (p = name; p != NULL && *p != 0; p++) {
    198     if (*p == '.') {
    199       ndots++;
    200     }
    201   }
    202 
    203   /* Label count is 1 greater than ndots */
    204   return ndots + 1;
    205 }
    206 
    207 ares_status_t ares_search_name_list(const ares_channel_t *channel,
    208                                     const char *name, char ***names,
    209                                     size_t *names_len)
    210 {
    211   ares_status_t status;
    212   char        **list     = NULL;
    213   size_t        list_len = 0;
    214   char         *alias    = NULL;
    215   size_t        ndots    = 0;
    216   size_t        idx      = 0;
    217   size_t        i;
    218 
    219   /* Perform HOSTALIASES resolution */
    220   status = ares_lookup_hostaliases(channel, name, &alias);
    221   if (status == ARES_SUCCESS) {
    222     /* If hostalias succeeds, there is no searching, it is used as-is */
    223     list_len = 1;
    224     list     = ares_malloc_zero(sizeof(*list) * list_len);
    225     if (list == NULL) {
    226       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    227       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    228     }
    229     list[0] = alias;
    230     alias   = NULL;
    231     goto done;
    232   } else if (status != ARES_ENOTFOUND) {
    233     goto done;
    234   }
    235 
    236   /* See if searching is eligible at all, if not, look up as-is only */
    237   if (!ares_search_eligible(channel, name)) {
    238     list_len = 1;
    239     list     = ares_malloc_zero(sizeof(*list) * list_len);
    240     if (list == NULL) {
    241       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    242       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    243     }
    244     list[0] = ares_strdup(name);
    245     if (list[0] == NULL) {
    246       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    247     } else {
    248       status = ARES_SUCCESS;
    249     }
    250     goto done;
    251   }
    252 
    253   /* Count the number of dots in name, 1 less than label count */
    254   ndots = ares_name_label_cnt(name);
    255   if (ndots > 0) {
    256     ndots--;
    257   }
    258 
    259   /* Allocate an entry for each search domain, plus one for as-is */
    260   list_len = channel->ndomains + 1;
    261   list     = ares_malloc_zero(sizeof(*list) * list_len);
    262   if (list == NULL) {
    263     status = ARES_ENOMEM;
    264     goto done;
    265   }
    266 
    267   /* Set status here, its possible there are no search domains at all, so
    268    * status may be ARES_ENOTFOUND from ares_lookup_hostaliases(). */
    269   status = ARES_SUCCESS;
    270 
    271   /* Try as-is first */
    272   if (ndots >= channel->ndots) {
    273     list[idx] = ares_strdup(name);
    274     if (list[idx] == NULL) {
    275       status = ARES_ENOMEM;
    276       goto done;
    277     }
    278     idx++;
    279   }
    280 
    281   /* Append each search suffix to the name */
    282   for (i = 0; i < channel->ndomains; i++) {
    283     status = ares_cat_domain(name, channel->domains[i], &list[idx]);
    284     if (status != ARES_SUCCESS) {
    285       goto done;
    286     }
    287     idx++;
    288   }
    289 
    290   /* Try as-is last */
    291   if (ndots < channel->ndots) {
    292     list[idx] = ares_strdup(name);
    293     if (list[idx] == NULL) {
    294       status = ARES_ENOMEM;
    295       goto done;
    296     }
    297     idx++;
    298   }
    299 
    300 
    301 done:
    302   if (status == ARES_SUCCESS) {
    303     *names     = list;
    304     *names_len = list_len;
    305   } else {
    306     ares_strsplit_free(list, list_len);
    307   }
    308 
    309   ares_free(alias);
    310   return status;
    311 }
    312 
    313 static ares_status_t ares_search_int(ares_channel_t          *channel,
    314                                      const ares_dns_record_t *dnsrec,
    315                                      ares_callback_dnsrec callback, void *arg)
    316 {
    317   struct search_query *squery = NULL;
    318   const char          *name;
    319   ares_status_t        status       = ARES_SUCCESS;
    320   ares_bool_t          skip_cleanup = ARES_FALSE;
    321 
    322   /* Extract the name for the search. Note that searches are only supported for
    323    * DNS records containing a single query.
    324    */
    325   if (ares_dns_record_query_cnt(dnsrec) != 1) {
    326     status = ARES_EBADQUERY;
    327     goto fail;
    328   }
    329 
    330   status = ares_dns_record_query_get(dnsrec, 0, &name, NULL, NULL);
    331   if (status != ARES_SUCCESS) {
    332     goto fail;
    333   }
    334 
    335   /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
    336   if (ares_is_onion_domain(name)) {
    337     status = ARES_ENOTFOUND;
    338     goto fail;
    339   }
    340 
    341   /* Allocate a search_query structure to hold the state necessary for
    342    * doing multiple lookups.
    343    */
    344   squery = ares_malloc_zero(sizeof(*squery));
    345   if (squery == NULL) {
    346     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    347     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
    348   }
    349 
    350   squery->channel = channel;
    351 
    352   /* Duplicate DNS record since, name will need to be rewritten */
    353   squery->dnsrec = ares_dns_record_duplicate(dnsrec);
    354   if (squery->dnsrec == NULL) {
    355     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    356     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
    357   }
    358 
    359   squery->callback        = callback;
    360   squery->arg             = arg;
    361   squery->timeouts        = 0;
    362   squery->ever_got_nodata = ARES_FALSE;
    363 
    364   status =
    365     ares_search_name_list(channel, name, &squery->names, &squery->names_cnt);
    366   if (status != ARES_SUCCESS) {
    367     goto fail;
    368   }
    369 
    370   status = ares_search_next(channel, squery, &skip_cleanup);
    371   if (status != ARES_SUCCESS) {
    372     goto fail;
    373   }
    374 
    375   return status;
    376 
    377 fail:
    378   if (!skip_cleanup) {
    379     squery_free(squery);
    380     callback(arg, status, 0, NULL);
    381   }
    382   return status;
    383 }
    384 
    385 /* Callback argument structure passed to ares_dnsrec_convert_cb(). */
    386 typedef struct {
    387   ares_callback callback;
    388   void         *arg;
    389 } dnsrec_convert_arg_t;
    390 
    391 /*! Function to create callback arg for converting from ares_callback_dnsrec
    392  *  to ares_calback */
    393 void *ares_dnsrec_convert_arg(ares_callback callback, void *arg)
    394 {
    395   dnsrec_convert_arg_t *carg = ares_malloc_zero(sizeof(*carg));
    396   if (carg == NULL) {
    397     return NULL;
    398   }
    399   carg->callback = callback;
    400   carg->arg      = arg;
    401   return carg;
    402 }
    403 
    404 /*! Callback function used to convert from the ares_callback_dnsrec prototype to
    405  *  the ares_callback prototype, by writing the result and passing that to
    406  *  the inner callback.
    407  */
    408 void ares_dnsrec_convert_cb(void *arg, ares_status_t status, size_t timeouts,
    409                             const ares_dns_record_t *dnsrec)
    410 {
    411   dnsrec_convert_arg_t *carg = arg;
    412   unsigned char        *abuf = NULL;
    413   size_t                alen = 0;
    414 
    415   if (dnsrec != NULL) {
    416     ares_status_t mystatus = ares_dns_write(dnsrec, &abuf, &alen);
    417     if (mystatus != ARES_SUCCESS) {
    418       status = mystatus;
    419     }
    420   }
    421 
    422   carg->callback(carg->arg, (int)status, (int)timeouts, abuf, (int)alen);
    423 
    424   ares_free(abuf);
    425   ares_free(carg);
    426 }
    427 
    428 /* Search for a DNS name with given class and type. Wrapper around
    429  * ares_search_int() where the DNS record to search is first constructed.
    430  */
    431 void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
    432                  int type, ares_callback callback, void *arg)
    433 {
    434   ares_status_t      status;
    435   ares_dns_record_t *dnsrec = NULL;
    436   size_t             max_udp_size;
    437   ares_dns_flags_t   rd_flag;
    438   void              *carg = NULL;
    439   if (channel == NULL || name == NULL) {
    440     return;
    441   }
    442 
    443   /* For now, ares_search_int() uses the ares_callback prototype. We need to
    444    * wrap the callback passed to this function in ares_dnsrec_convert_cb, to
    445    * convert from ares_callback_dnsrec to ares_callback. Allocate the convert
    446    * arg structure here.
    447    */
    448   carg = ares_dnsrec_convert_arg(callback, arg);
    449   if (carg == NULL) {
    450     callback(arg, ARES_ENOMEM, 0, NULL, 0);
    451     return;
    452   }
    453 
    454   rd_flag      = !(channel->flags & ARES_FLAG_NORECURSE) ? ARES_FLAG_RD : 0;
    455   max_udp_size = (channel->flags & ARES_FLAG_EDNS) ? channel->ednspsz : 0;
    456   status       = ares_dns_record_create_query(
    457     &dnsrec, name, (ares_dns_class_t)dnsclass, (ares_dns_rec_type_t)type, 0,
    458     rd_flag, max_udp_size);
    459   if (status != ARES_SUCCESS) {
    460     callback(arg, (int)status, 0, NULL, 0);
    461     ares_free(carg);
    462     return;
    463   }
    464 
    465   ares_channel_lock(channel);
    466   ares_search_int(channel, dnsrec, ares_dnsrec_convert_cb, carg);
    467   ares_channel_unlock(channel);
    468 
    469   ares_dns_record_destroy(dnsrec);
    470 }
    471 
    472 /* Search for a DNS record. Wrapper around ares_search_int(). */
    473 ares_status_t ares_search_dnsrec(ares_channel_t          *channel,
    474                                  const ares_dns_record_t *dnsrec,
    475                                  ares_callback_dnsrec callback, void *arg)
    476 {
    477   ares_status_t status;
    478 
    479   if (channel == NULL || dnsrec == NULL || callback == NULL) {
    480     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
    481   }
    482 
    483   ares_channel_lock(channel);
    484   status = ares_search_int(channel, dnsrec, callback, arg);
    485   ares_channel_unlock(channel);
    486 
    487   return status;
    488 }
    489 
    490 /* Concatenate two domains. */
    491 ares_status_t ares_cat_domain(const char *name, const char *domain, char **s)
    492 {
    493   size_t nlen = ares_strlen(name);
    494   size_t dlen = ares_strlen(domain);
    495 
    496   *s = ares_malloc(nlen + 1 + dlen + 1);
    497   if (!*s) {
    498     return ARES_ENOMEM;
    499   }
    500   memcpy(*s, name, nlen);
    501   (*s)[nlen] = '.';
    502   if (ares_streq(domain, ".")) {
    503     /* Avoid appending the root domain to the separator, which would set *s to
    504        an ill-formed value (ending in two consecutive dots). */
    505     dlen = 0;
    506   }
    507   memcpy(*s + nlen + 1, domain, dlen);
    508   (*s)[nlen + 1 + dlen] = 0;
    509   return ARES_SUCCESS;
    510 }
    511 
    512 ares_status_t ares_lookup_hostaliases(const ares_channel_t *channel,
    513                                       const char *name, char **alias)
    514 {
    515   ares_status_t status      = ARES_SUCCESS;
    516   const char   *hostaliases = NULL;
    517   ares_buf_t   *buf         = NULL;
    518   ares_array_t *lines       = NULL;
    519   size_t        num;
    520   size_t        i;
    521 
    522   if (channel == NULL || name == NULL || alias == NULL) {
    523     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
    524   }
    525 
    526   *alias = NULL;
    527 
    528   /* Configuration says to not perform alias lookup */
    529   if (channel->flags & ARES_FLAG_NOALIASES) {
    530     return ARES_ENOTFOUND;
    531   }
    532 
    533   /* If a domain has a '.', its not allowed to perform an alias lookup */
    534   if (strchr(name, '.') != NULL) {
    535     return ARES_ENOTFOUND;
    536   }
    537 
    538   hostaliases = getenv("HOSTALIASES");
    539   if (hostaliases == NULL) {
    540     status = ARES_ENOTFOUND;
    541     goto done;
    542   }
    543 
    544   buf = ares_buf_create();
    545   if (buf == NULL) {
    546     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    547     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    548   }
    549 
    550   status = ares_buf_load_file(hostaliases, buf);
    551   if (status != ARES_SUCCESS) {
    552     goto done;
    553   }
    554 
    555   /* The HOSTALIASES file is structured as one alias per line.  The first
    556    * field in the line is the simple hostname with no periods, followed by
    557    * whitespace, then the full domain name, e.g.:
    558    *
    559    * c-ares  www.c-ares.org
    560    * curl    www.curl.se
    561    */
    562 
    563   status = ares_buf_split(buf, (const unsigned char *)"\n", 1,
    564                           ARES_BUF_SPLIT_TRIM, 0, &lines);
    565   if (status != ARES_SUCCESS) {
    566     goto done;
    567   }
    568 
    569   num = ares_array_len(lines);
    570   for (i = 0; i < num; i++) {
    571     ares_buf_t **bufptr       = ares_array_at(lines, i);
    572     ares_buf_t  *line         = *bufptr;
    573     char         hostname[64] = "";
    574     char         fqdn[256]    = "";
    575 
    576     /* Pull off hostname */
    577     ares_buf_tag(line);
    578     ares_buf_consume_nonwhitespace(line);
    579     if (ares_buf_tag_fetch_string(line, hostname, sizeof(hostname)) !=
    580         ARES_SUCCESS) {
    581       continue;
    582     }
    583 
    584     /* Match hostname */
    585     if (!ares_strcaseeq(hostname, name)) {
    586       continue;
    587     }
    588 
    589     /* consume whitespace */
    590     ares_buf_consume_whitespace(line, ARES_TRUE);
    591 
    592     /* pull off fqdn */
    593     ares_buf_tag(line);
    594     ares_buf_consume_nonwhitespace(line);
    595     if (ares_buf_tag_fetch_string(line, fqdn, sizeof(fqdn)) != ARES_SUCCESS ||
    596         ares_strlen(fqdn) == 0) {
    597       continue;
    598     }
    599 
    600     /* Validate characterset */
    601     if (!ares_is_hostname(fqdn)) {
    602       continue;
    603     }
    604 
    605     *alias = ares_strdup(fqdn);
    606     if (*alias == NULL) {
    607       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    608       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
    609     }
    610 
    611     /* Good! */
    612     status = ARES_SUCCESS;
    613     goto done;
    614   }
    615 
    616   status = ARES_ENOTFOUND;
    617 
    618 done:
    619   ares_buf_destroy(buf);
    620   ares_array_destroy(lines);
    621 
    622   return status;
    623 }