quickjs-tart

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

ares_sysconfig_mac.c (11989B)


      1 /* MIT License
      2  *
      3  * Copyright (c) 2024 The c-ares project and its contributors
      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 
     27 #ifdef __APPLE__
     28 
     29 /* The DNS configuration for apple is stored in the system configuration
     30  * database.  Apple does provide an emulated `/etc/resolv.conf` on MacOS (but
     31  * not iOS), it cannot, however, represent the entirety of the DNS
     32  * configuration.  Alternatively, libresolv could be used to also retrieve some
     33  * system configuration, but it too is not capable of retrieving the entirety
     34  * of the DNS configuration.
     35  *
     36  * Attempts to use the preferred public API of `SCDynamicStoreCreate()` and
     37  * friends yielded incomplete DNS information.  Instead, that leaves some apple
     38  * "internal" symbols from `configd` that we need to access in order to get the
     39  * entire configuration.  We can see that we're not the only ones to do this as
     40  * Google Chrome also does:
     41  * https://chromium.googlesource.com/chromium/src/+/HEAD/net/dns/dns_config_watcher_mac.cc
     42  * These internal functions are what `libresolv` and `scutil` use to retrieve
     43  * the dns configuration.  Since these symbols are not publicly available, we
     44  * will dynamically load the symbols from `libSystem` and import the `dnsinfo.h`
     45  * private header extracted from:
     46  * https://opensource.apple.com/source/configd/configd-1109.140.1/dnsinfo/dnsinfo.h
     47  */
     48 
     49 /* The apple header uses anonymous unions which came with C11 */
     50 #  if defined(__clang__)
     51 #    pragma GCC diagnostic push
     52 #    pragma GCC diagnostic ignored "-Wc11-extensions"
     53 #  endif
     54 
     55 #  include "ares_private.h"
     56 #  include <stdio.h>
     57 #  include <stdlib.h>
     58 #  include <string.h>
     59 #  include <dlfcn.h>
     60 #  include <arpa/inet.h>
     61 #  include "thirdparty/apple/dnsinfo.h"
     62 #  include <AvailabilityMacros.h>
     63 #  if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */
     64 #    include <SystemConfiguration/SCNetworkConfiguration.h>
     65 #  endif
     66 
     67 typedef struct {
     68   void *handle;
     69   dns_config_t *(*dns_configuration_copy)(void);
     70   void (*dns_configuration_free)(dns_config_t *config);
     71 } dnsinfo_t;
     72 
     73 static void dnsinfo_destroy(dnsinfo_t *dnsinfo)
     74 {
     75   if (dnsinfo == NULL) {
     76     return;
     77   }
     78 
     79   if (dnsinfo->handle) {
     80     dlclose(dnsinfo->handle);
     81   }
     82 
     83   ares_free(dnsinfo);
     84 }
     85 
     86 static ares_status_t dnsinfo_init(dnsinfo_t **dnsinfo_out)
     87 {
     88   dnsinfo_t    *dnsinfo = NULL;
     89   ares_status_t status  = ARES_SUCCESS;
     90   size_t        i;
     91   const char   *searchlibs[] = {
     92     "/usr/lib/libSystem.dylib",
     93     "/System/Library/Frameworks/SystemConfiguration.framework/"
     94       "SystemConfiguration",
     95     NULL
     96   };
     97 
     98   if (dnsinfo_out == NULL) {
     99     status = ARES_EFORMERR;
    100     goto done;
    101   }
    102 
    103   *dnsinfo_out = NULL;
    104 
    105   dnsinfo = ares_malloc_zero(sizeof(*dnsinfo));
    106   if (dnsinfo == NULL) {
    107     status = ARES_ENOMEM;
    108     goto done;
    109   }
    110 
    111   for (i = 0; searchlibs[i] != NULL; i++) {
    112     dnsinfo->handle = dlopen(searchlibs[i], RTLD_LAZY /* | RTLD_NOLOAD */);
    113     if (dnsinfo->handle == NULL) {
    114       /* Fail, loop */
    115       continue;
    116     }
    117 
    118     dnsinfo->dns_configuration_copy = (dns_config_t * (*)(void))
    119       dlsym(dnsinfo->handle, "dns_configuration_copy");
    120 
    121     dnsinfo->dns_configuration_free = (void (*)(dns_config_t *))dlsym(
    122       dnsinfo->handle, "dns_configuration_free");
    123 
    124     if (dnsinfo->dns_configuration_copy != NULL &&
    125         dnsinfo->dns_configuration_free != NULL) {
    126       break;
    127     }
    128 
    129     /* Fail, loop */
    130     dlclose(dnsinfo->handle);
    131     dnsinfo->handle = NULL;
    132   }
    133 
    134 
    135   if (dnsinfo->dns_configuration_copy == NULL ||
    136       dnsinfo->dns_configuration_free == NULL) {
    137     status = ARES_ESERVFAIL;
    138     goto done;
    139   }
    140 
    141 
    142 done:
    143   if (status == ARES_SUCCESS) {
    144     *dnsinfo_out = dnsinfo;
    145   } else {
    146     dnsinfo_destroy(dnsinfo);
    147   }
    148 
    149   return status;
    150 }
    151 
    152 static ares_bool_t search_is_duplicate(const ares_sysconfig_t *sysconfig,
    153                                        const char             *name)
    154 {
    155   size_t i;
    156   for (i = 0; i < sysconfig->ndomains; i++) {
    157     if (ares_strcaseeq(sysconfig->domains[i], name)) {
    158       return ARES_TRUE;
    159     }
    160   }
    161   return ARES_FALSE;
    162 }
    163 
    164 static ares_status_t read_resolver(const ares_channel_t *channel,
    165                                    const dns_resolver_t *resolver,
    166                                    ares_sysconfig_t     *sysconfig)
    167 {
    168   int            i;
    169   unsigned short port   = 0;
    170   ares_status_t  status = ARES_SUCCESS;
    171 
    172 #  if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */
    173   /* XXX: resolver->domain is for domain-specific servers.  When we implement
    174    *      this support, we'll want to use this.  But for now, we're going to
    175    *      skip any servers which set this since we can't properly route.
    176    *      MacOS used to use this setting for a different purpose in the
    177    *      past however, so on versions of MacOS < 10.8 just ignore this
    178    *      completely. */
    179   if (resolver->domain != NULL) {
    180     return ARES_SUCCESS;
    181   }
    182 #  endif
    183 
    184 #  if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */
    185   /* Check to see if DNS server should be used, base this on if the server is
    186    * reachable or can be reachable automatically if we send traffic that
    187    * direction. */
    188   if (!(resolver->reach_flags &
    189         (kSCNetworkFlagsReachable |
    190          kSCNetworkReachabilityFlagsConnectionOnTraffic))) {
    191     return ARES_SUCCESS;
    192   }
    193 #  endif
    194 
    195   /* NOTE: it doesn't look like resolver->flags is relevant */
    196 
    197   /* If there's no nameservers, nothing to do */
    198   if (resolver->n_nameserver <= 0) {
    199     return ARES_SUCCESS;
    200   }
    201 
    202   /* Default port */
    203   port = resolver->port;
    204 
    205   /* Append search list */
    206   if (resolver->n_search > 0) {
    207     char **new_domains = ares_realloc_zero(
    208       sysconfig->domains, sizeof(*sysconfig->domains) * sysconfig->ndomains,
    209       sizeof(*sysconfig->domains) *
    210         (sysconfig->ndomains + (size_t)resolver->n_search));
    211     if (new_domains == NULL) {
    212       return ARES_ENOMEM;
    213     }
    214     sysconfig->domains = new_domains;
    215 
    216     for (i = 0; i < resolver->n_search; i++) {
    217       const char *search;
    218       /* UBSAN: copy pointer using memcpy due to misalignment */
    219       memcpy(&search, resolver->search + i, sizeof(search));
    220 
    221       /* Skip duplicates */
    222       if (search_is_duplicate(sysconfig, search)) {
    223         continue;
    224       }
    225       sysconfig->domains[sysconfig->ndomains] = ares_strdup(search);
    226       if (sysconfig->domains[sysconfig->ndomains] == NULL) {
    227         return ARES_ENOMEM;
    228       }
    229       sysconfig->ndomains++;
    230     }
    231   }
    232 
    233   /* NOTE: we're going to skip importing the sort addresses for now.  Its
    234    *       likely not used, its not obvious how to even configure such a thing.
    235    */
    236 #  if 0
    237   for (i=0; i<resolver->n_sortaddr; i++) {
    238     char val[256];
    239     inet_ntop(AF_INET, &resolver->sortaddr[i]->address, val, sizeof(val));
    240     printf("\t\t%s/", val);
    241     inet_ntop(AF_INET, &resolver->sortaddr[i]->mask, val, sizeof(val));
    242     printf("%s\n", val);
    243   }
    244 #  endif
    245 
    246   if (resolver->options != NULL) {
    247     status = ares_sysconfig_set_options(sysconfig, resolver->options);
    248     if (status != ARES_SUCCESS) {
    249       return status;
    250     }
    251   }
    252 
    253   /* NOTE:
    254    *   - resolver->timeout appears unused, always 0, so we ignore this
    255    *   - resolver->service_identifier doesn't appear relevant to us
    256    *   - resolver->cid also isn't relevant
    257    *   - resolver->if_name we won't use since it isn't available in MacOS 10.8
    258    *     or earlier, use resolver->if_index instead to then lookup the name.
    259    */
    260 
    261   /* XXX: resolver->search_order appears like it might be relevant, we might
    262    * need to sort the resulting list by this metric if we find in the future we
    263    * need to.  That said, due to the automatic re-sorting we do, I'm not sure it
    264    * matters.  Here's an article on this search order stuff:
    265    *      https://www.cnet.com/tech/computing/os-x-10-6-3-and-dns-server-priority-changes/
    266    */
    267 
    268   for (i = 0; i < resolver->n_nameserver; i++) {
    269     struct ares_addr       addr;
    270     unsigned short         addrport;
    271     const struct sockaddr *sockaddr;
    272     char                   if_name_str[256] = "";
    273     const char            *if_name          = NULL;
    274 
    275     /* UBSAN alignment workaround to fetch memory address */
    276     memcpy(&sockaddr, resolver->nameserver + i, sizeof(sockaddr));
    277 
    278     if (!ares_sockaddr_to_ares_addr(&addr, &addrport, sockaddr)) {
    279       continue;
    280     }
    281 
    282     if (addrport == 0) {
    283       addrport = port;
    284     }
    285 
    286     if (channel->sock_funcs.aif_indextoname != NULL) {
    287       if_name = channel->sock_funcs.aif_indextoname(
    288         resolver->if_index, if_name_str, sizeof(if_name_str),
    289         channel->sock_func_cb_data);
    290     }
    291 
    292     status = ares_sconfig_append(channel, &sysconfig->sconfig, &addr, addrport,
    293                                  addrport, if_name);
    294     if (status != ARES_SUCCESS) {
    295       return status;
    296     }
    297   }
    298 
    299   return status;
    300 }
    301 
    302 static ares_status_t read_resolvers(const ares_channel_t *channel,
    303                                     dns_resolver_t **resolvers, int nresolvers,
    304                                     ares_sysconfig_t *sysconfig)
    305 {
    306   ares_status_t status = ARES_SUCCESS;
    307   int           i;
    308 
    309   for (i = 0; status == ARES_SUCCESS && i < nresolvers; i++) {
    310     const dns_resolver_t *resolver_ptr;
    311 
    312     /* UBSAN doesn't like that this is unaligned, lets use memcpy to get the
    313      * address.  Equivalent to:
    314      *   resolver = resolvers[i]
    315      */
    316     memcpy(&resolver_ptr, resolvers + i, sizeof(resolver_ptr));
    317 
    318     status = read_resolver(channel, resolver_ptr, sysconfig);
    319   }
    320 
    321   return status;
    322 }
    323 
    324 ares_status_t ares_init_sysconfig_macos(const ares_channel_t *channel,
    325                                         ares_sysconfig_t     *sysconfig)
    326 {
    327   dnsinfo_t    *dnsinfo = NULL;
    328   dns_config_t *sc_dns  = NULL;
    329   ares_status_t status  = ARES_SUCCESS;
    330 
    331   status = dnsinfo_init(&dnsinfo);
    332 
    333   if (status != ARES_SUCCESS) {
    334     goto done;
    335   }
    336 
    337   sc_dns = dnsinfo->dns_configuration_copy();
    338   if (sc_dns == NULL) {
    339     status = ARES_ESERVFAIL;
    340     goto done;
    341   }
    342 
    343   /* There are `resolver`, `scoped_resolver`, and `service_specific_resolver`
    344    * settings. The `scoped_resolver` settings appear to be already available via
    345    * the `resolver` settings and likely are only relevant to link-local dns
    346    * servers which we can already detect via the address itself, so we'll ignore
    347    * the `scoped_resolver` section.  It isn't clear what the
    348    * `service_specific_resolver` is used for, I haven't personally seen it
    349    * in use so we'll ignore this until at some point where we find we need it.
    350    * Likely this wasn't available via `/etc/resolv.conf` nor `libresolv` anyhow
    351    * so its not worse to prior configuration methods, worst case. */
    352 
    353   status =
    354     read_resolvers(channel, sc_dns->resolver, sc_dns->n_resolver, sysconfig);
    355 
    356 done:
    357   if (dnsinfo) {
    358     dnsinfo->dns_configuration_free(sc_dns);
    359     dnsinfo_destroy(dnsinfo);
    360   }
    361   return status;
    362 }
    363 
    364 #  if defined(__clang__)
    365 #    pragma GCC diagnostic pop
    366 #  endif
    367 
    368 #else
    369 
    370 /* Prevent compiler warnings due to empty translation unit */
    371 typedef int make_iso_compilers_happy;
    372 
    373 #endif