quickjs-tart

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

ares_sysconfig_win.c (19944B)


      1 /* MIT License
      2  *
      3  * Copyright (c) 1998 Massachusetts Institute of Technology
      4  * Copyright (c) 2007 Daniel Stenberg
      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_SYS_PARAM_H
     31 #  include <sys/param.h>
     32 #endif
     33 
     34 #ifdef HAVE_NETINET_IN_H
     35 #  include <netinet/in.h>
     36 #endif
     37 
     38 #ifdef HAVE_NETDB_H
     39 #  include <netdb.h>
     40 #endif
     41 
     42 #ifdef HAVE_ARPA_INET_H
     43 #  include <arpa/inet.h>
     44 #endif
     45 
     46 #if defined(USE_WINSOCK)
     47 #  if defined(HAVE_IPHLPAPI_H)
     48 #    include <iphlpapi.h>
     49 #  endif
     50 #  if defined(HAVE_NETIOAPI_H)
     51 #    include <netioapi.h>
     52 #  endif
     53 #endif
     54 
     55 #include "ares_inet_net_pton.h"
     56 
     57 #if defined(USE_WINSOCK)
     58 /*
     59  * get_REG_SZ()
     60  *
     61  * Given a 'hKey' handle to an open registry key and a 'leafKeyName' pointer
     62  * to the name of the registry leaf key to be queried, fetch it's string
     63  * value and return a pointer in *outptr to a newly allocated memory area
     64  * holding it as a null-terminated string.
     65  *
     66  * Returns 0 and nullifies *outptr upon inability to return a string value.
     67  *
     68  * Returns 1 and sets *outptr when returning a dynamically allocated string.
     69  *
     70  * Supported on Windows NT 3.5 and newer.
     71  */
     72 static ares_bool_t get_REG_SZ(HKEY hKey, const char *leafKeyName, char **outptr)
     73 {
     74   DWORD size = 0;
     75   int   res;
     76 
     77   *outptr = NULL;
     78 
     79   /* Find out size of string stored in registry */
     80   res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, NULL, &size);
     81   if ((res != ERROR_SUCCESS && res != ERROR_MORE_DATA) || !size) {
     82     return ARES_FALSE;
     83   }
     84 
     85   /* Allocate buffer of indicated size plus one given that string
     86      might have been stored without null termination */
     87   *outptr = ares_malloc(size + 1);
     88   if (!*outptr) {
     89     return ARES_FALSE;
     90   }
     91 
     92   /* Get the value for real */
     93   res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, (unsigned char *)*outptr,
     94                          &size);
     95   if ((res != ERROR_SUCCESS) || (size == 1)) {
     96     ares_free(*outptr);
     97     *outptr = NULL;
     98     return ARES_FALSE;
     99   }
    100 
    101   /* Null terminate buffer always */
    102   *(*outptr + size) = '\0';
    103 
    104   return ARES_TRUE;
    105 }
    106 
    107 static void commanjoin(char **dst, const char * const src, const size_t len)
    108 {
    109   char  *newbuf;
    110   size_t newsize;
    111 
    112   /* 1 for terminating 0 and 2 for , and terminating 0 */
    113   newsize = len + (*dst ? (ares_strlen(*dst) + 2) : 1);
    114   newbuf  = ares_realloc(*dst, newsize);
    115   if (!newbuf) {
    116     return;
    117   }
    118   if (*dst == NULL) {
    119     *newbuf = '\0';
    120   }
    121   *dst = newbuf;
    122   if (ares_strlen(*dst) != 0) {
    123     strcat(*dst, ",");
    124   }
    125   strncat(*dst, src, len);
    126 }
    127 
    128 /*
    129  * commajoin()
    130  *
    131  * RTF code.
    132  */
    133 static void commajoin(char **dst, const char *src)
    134 {
    135   commanjoin(dst, src, ares_strlen(src));
    136 }
    137 
    138 /* A structure to hold the string form of IPv4 and IPv6 addresses so we can
    139  * sort them by a metric.
    140  */
    141 typedef struct {
    142   /* The metric we sort them by. */
    143   ULONG  metric;
    144 
    145   /* Original index of the item, used as a secondary sort parameter to make
    146    * qsort() stable if the metrics are equal */
    147   size_t orig_idx;
    148 
    149   /* Room enough for the string form of any IPv4 or IPv6 address that
    150    * ares_inet_ntop() will create.  Based on the existing c-ares practice.
    151    */
    152   char   text[INET6_ADDRSTRLEN + 8 + 64]; /* [%s]:NNNNN%iface */
    153 } Address;
    154 
    155 /* Sort Address values \a left and \a right by metric, returning the usual
    156  * indicators for qsort().
    157  */
    158 static int compareAddresses(const void *arg1, const void *arg2)
    159 {
    160   const Address * const left  = arg1;
    161   const Address * const right = arg2;
    162   /* Lower metric the more preferred */
    163   if (left->metric < right->metric) {
    164     return -1;
    165   }
    166   if (left->metric > right->metric) {
    167     return 1;
    168   }
    169   /* If metrics are equal, lower original index more preferred */
    170   if (left->orig_idx < right->orig_idx) {
    171     return -1;
    172   }
    173   if (left->orig_idx > right->orig_idx) {
    174     return 1;
    175   }
    176   return 0;
    177 }
    178 
    179 #if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__)
    180 /* There can be multiple routes to "the Internet".  And there can be different
    181  * DNS servers associated with each of the interfaces that offer those routes.
    182  * We have to assume that any DNS server can serve any request.  But, some DNS
    183  * servers may only respond if requested over their associated interface.  But
    184  * we also want to use "the preferred route to the Internet" whenever possible
    185  * (and not use DNS servers on a non-preferred route even by forcing request
    186  * to go out on the associated non-preferred interface).  i.e. We want to use
    187  * the DNS servers associated with the same interface that we would use to
    188  * make a general request to anything else.
    189  *
    190  * But, Windows won't sort the DNS servers by the metrics associated with the
    191  * routes and interfaces _even_ though it obviously sends IP packets based on
    192  * those same routes and metrics.  So, we must do it ourselves.
    193  *
    194  * So, we sort the DNS servers by the same metric values used to determine how
    195  * an outgoing IP packet will go, thus effectively using the DNS servers
    196  * associated with the interface that the DNS requests themselves will
    197  * travel.  This gives us optimal routing and avoids issues where DNS servers
    198  * won't respond to requests that don't arrive via some specific subnetwork
    199  * (and thus some specific interface).
    200  *
    201  * This function computes the metric we use to sort.  On the interface
    202  * identified by \a luid, it determines the best route to \a dest and combines
    203  * that route's metric with \a interfaceMetric to compute a metric for the
    204  * destination address on that interface.  This metric can be used as a weight
    205  * to sort the DNS server addresses associated with each interface (lower is
    206  * better).
    207  *
    208  * Note that by restricting the route search to the specific interface with
    209  * which the DNS servers are associated, this function asks the question "What
    210  * is the metric for sending IP packets to this DNS server?" which allows us
    211  * to sort the DNS servers correctly.
    212  */
    213 static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */
    214                                 const SOCKADDR_INET * const dest,
    215                                 const ULONG                 interfaceMetric)
    216 {
    217   MIB_IPFORWARD_ROW2 row;
    218   SOCKADDR_INET      ignored;
    219   if (GetBestRoute2(/* The interface to use.  The index is ignored since we are
    220                      * passing a LUID.
    221                      */
    222                     luid, 0,
    223                     /* No specific source address. */
    224                     NULL,
    225                     /* Our destination address. */
    226                     dest,
    227                     /* No options. */
    228                     0,
    229                     /* The route row. */
    230                     &row,
    231                     /* The best source address, which we don't need. */
    232                     &ignored) != NO_ERROR
    233       /* If the metric is "unused" (-1) or too large for us to add the two
    234        * metrics, use the worst possible, thus sorting this last.
    235        */
    236       || row.Metric == (ULONG)-1 ||
    237       row.Metric > ((ULONG)-1) - interfaceMetric) {
    238     /* Return the worst possible metric. */
    239     return (ULONG)-1;
    240   }
    241 
    242   /* Return the metric value from that row, plus the interface metric.
    243    *
    244    * See
    245    * http://msdn.microsoft.com/en-us/library/windows/desktop/aa814494(v=vs.85).aspx
    246    * which describes the combination as a "sum".
    247    */
    248   return row.Metric + interfaceMetric;
    249 }
    250 #endif
    251 
    252 /*
    253  * get_DNS_Windows()
    254  *
    255  * Locates DNS info using GetAdaptersAddresses() function from the Internet
    256  * Protocol Helper (IP Helper) API. When located, this returns a pointer
    257  * in *outptr to a newly allocated memory area holding a null-terminated
    258  * string with a space or comma separated list of DNS IP addresses.
    259  *
    260  * Returns 0 and nullifies *outptr upon inability to return DNSes string.
    261  *
    262  * Returns 1 and sets *outptr when returning a dynamically allocated string.
    263  *
    264  * Implementation supports Windows XP and newer.
    265  */
    266 #  define IPAA_INITIAL_BUF_SZ 15 * 1024
    267 #  define IPAA_MAX_TRIES      3
    268 
    269 static ares_bool_t get_DNS_Windows(char **outptr)
    270 {
    271   IP_ADAPTER_DNS_SERVER_ADDRESS *ipaDNSAddr;
    272   IP_ADAPTER_ADDRESSES          *ipaa;
    273   IP_ADAPTER_ADDRESSES          *newipaa;
    274   IP_ADAPTER_ADDRESSES          *ipaaEntry;
    275   ULONG                          ReqBufsz  = IPAA_INITIAL_BUF_SZ;
    276   ULONG                          Bufsz     = IPAA_INITIAL_BUF_SZ;
    277   ULONG                          AddrFlags = 0;
    278   int                            trying    = IPAA_MAX_TRIES;
    279   ULONG                          res;
    280 
    281   /* The capacity of addresses, in elements. */
    282   size_t                         addressesSize;
    283   /* The number of elements in addresses. */
    284   size_t                         addressesIndex = 0;
    285   /* The addresses we will sort. */
    286   Address                       *addresses;
    287 
    288   union {
    289     struct sockaddr     *sa;
    290     struct sockaddr_in  *sa4;
    291     struct sockaddr_in6 *sa6;
    292   } namesrvr;
    293 
    294   *outptr = NULL;
    295 
    296   ipaa = ares_malloc(Bufsz);
    297   if (!ipaa) {
    298     return ARES_FALSE;
    299   }
    300 
    301   /* Start with enough room for a few DNS server addresses and we'll grow it
    302    * as we encounter more.
    303    */
    304   addressesSize = 4;
    305   addresses     = (Address *)ares_malloc(sizeof(Address) * addressesSize);
    306   if (addresses == NULL) {
    307     /* We need room for at least some addresses to function. */
    308     ares_free(ipaa);
    309     return ARES_FALSE;
    310   }
    311 
    312   /* Usually this call succeeds with initial buffer size */
    313   res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz);
    314   if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) {
    315     goto done;
    316   }
    317 
    318   while ((res == ERROR_BUFFER_OVERFLOW) && (--trying)) {
    319     if (Bufsz < ReqBufsz) {
    320       newipaa = ares_realloc(ipaa, ReqBufsz);
    321       if (!newipaa) {
    322         goto done;
    323       }
    324       Bufsz = ReqBufsz;
    325       ipaa  = newipaa;
    326     }
    327     res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz);
    328     if (res == ERROR_SUCCESS) {
    329       break;
    330     }
    331   }
    332   if (res != ERROR_SUCCESS) {
    333     goto done;
    334   }
    335 
    336   for (ipaaEntry = ipaa; ipaaEntry; ipaaEntry = ipaaEntry->Next) {
    337     if (ipaaEntry->OperStatus != IfOperStatusUp) {
    338       continue;
    339     }
    340 
    341     /* For each interface, find any associated DNS servers as IPv4 or IPv6
    342      * addresses.  For each found address, find the best route to that DNS
    343      * server address _on_ _that_ _interface_ (at this moment in time) and
    344      * compute the resulting total metric, just as Windows routing will do.
    345      * Then, sort all the addresses found by the metric.
    346      */
    347     for (ipaDNSAddr = ipaaEntry->FirstDnsServerAddress; ipaDNSAddr != NULL;
    348          ipaDNSAddr = ipaDNSAddr->Next) {
    349       char ipaddr[INET6_ADDRSTRLEN] = "";
    350 
    351       namesrvr.sa = ipaDNSAddr->Address.lpSockaddr;
    352 
    353       if (namesrvr.sa->sa_family == AF_INET) {
    354         if ((namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_ANY) ||
    355             (namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_NONE)) {
    356           continue;
    357         }
    358 
    359         /* Allocate room for another address, if necessary, else skip. */
    360         if (addressesIndex == addressesSize) {
    361           const size_t    newSize = addressesSize + 4;
    362           Address * const newMem =
    363             (Address *)ares_realloc(addresses, sizeof(Address) * newSize);
    364           if (newMem == NULL) {
    365             continue;
    366           }
    367           addresses     = newMem;
    368           addressesSize = newSize;
    369         }
    370 
    371 #  if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__)
    372         /* OpenWatcom's builtin Windows SDK does not have a definition for
    373          * MIB_IPFORWARD_ROW2, and also does not allow the usage of SOCKADDR_INET
    374          * as a variable. Let's work around this by returning the worst possible
    375          * metric, but only when using the OpenWatcom compiler.
    376          * It may be worth investigating using a different version of the Windows
    377          * SDK with OpenWatcom in the future, though this may be fixed in OpenWatcom
    378          * 2.0.
    379          */
    380         addresses[addressesIndex].metric = getBestRouteMetric(
    381           &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)),
    382           ipaaEntry->Ipv4Metric);
    383 #  else
    384         addresses[addressesIndex].metric = (ULONG)-1;
    385 #  endif
    386 
    387         /* Record insertion index to make qsort stable */
    388         addresses[addressesIndex].orig_idx = addressesIndex;
    389 
    390         if (!ares_inet_ntop(AF_INET, &namesrvr.sa4->sin_addr, ipaddr,
    391                             sizeof(ipaddr))) {
    392           continue;
    393         }
    394         snprintf(addresses[addressesIndex].text,
    395                  sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr,
    396                  ntohs(namesrvr.sa4->sin_port));
    397         ++addressesIndex;
    398       } else if (namesrvr.sa->sa_family == AF_INET6) {
    399         unsigned int     ll_scope = 0;
    400         struct ares_addr addr;
    401 
    402         if (memcmp(&namesrvr.sa6->sin6_addr, &ares_in6addr_any,
    403                    sizeof(namesrvr.sa6->sin6_addr)) == 0) {
    404           continue;
    405         }
    406 
    407         /* Allocate room for another address, if necessary, else skip. */
    408         if (addressesIndex == addressesSize) {
    409           const size_t    newSize = addressesSize + 4;
    410           Address * const newMem =
    411             (Address *)ares_realloc(addresses, sizeof(Address) * newSize);
    412           if (newMem == NULL) {
    413             continue;
    414           }
    415           addresses     = newMem;
    416           addressesSize = newSize;
    417         }
    418 
    419         /* See if its link-local */
    420         memset(&addr, 0, sizeof(addr));
    421         addr.family = AF_INET6;
    422         memcpy(&addr.addr.addr6, &namesrvr.sa6->sin6_addr, 16);
    423         if (ares_addr_is_linklocal(&addr)) {
    424           ll_scope = ipaaEntry->Ipv6IfIndex;
    425         }
    426 
    427 #  if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__)
    428         addresses[addressesIndex].metric = getBestRouteMetric(
    429           &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)),
    430           ipaaEntry->Ipv6Metric);
    431 #  else
    432         addresses[addressesIndex].metric = (ULONG)-1;
    433 #  endif
    434 
    435         /* Record insertion index to make qsort stable */
    436         addresses[addressesIndex].orig_idx = addressesIndex;
    437 
    438         if (!ares_inet_ntop(AF_INET6, &namesrvr.sa6->sin6_addr, ipaddr,
    439                             sizeof(ipaddr))) {
    440           continue;
    441         }
    442 
    443         if (ll_scope) {
    444           snprintf(addresses[addressesIndex].text,
    445                    sizeof(addresses[addressesIndex].text), "[%s]:%u%%%u",
    446                    ipaddr, ntohs(namesrvr.sa6->sin6_port), ll_scope);
    447         } else {
    448           snprintf(addresses[addressesIndex].text,
    449                    sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr,
    450                    ntohs(namesrvr.sa6->sin6_port));
    451         }
    452         ++addressesIndex;
    453       } else {
    454         /* Skip non-IPv4/IPv6 addresses completely. */
    455         continue;
    456       }
    457     }
    458   }
    459 
    460   /* Sort all of the textual addresses by their metric (and original index if
    461    * metrics are equal). */
    462   qsort(addresses, addressesIndex, sizeof(*addresses), compareAddresses);
    463 
    464   /* Join them all into a single string, removing duplicates. */
    465   {
    466     size_t i;
    467     for (i = 0; i < addressesIndex; ++i) {
    468       size_t j;
    469       /* Look for this address text appearing previously in the results. */
    470       for (j = 0; j < i; ++j) {
    471         if (strcmp(addresses[j].text, addresses[i].text) == 0) {
    472           break;
    473         }
    474       }
    475       /* Iff we didn't emit this address already, emit it now. */
    476       if (j == i) {
    477         /* Add that to outptr (if we can). */
    478         commajoin(outptr, addresses[i].text);
    479       }
    480     }
    481   }
    482 
    483 done:
    484   ares_free(addresses);
    485 
    486   if (ipaa) {
    487     ares_free(ipaa);
    488   }
    489 
    490   if (!*outptr) {
    491     return ARES_FALSE;
    492   }
    493 
    494   return ARES_TRUE;
    495 }
    496 
    497 /*
    498  * get_SuffixList_Windows()
    499  *
    500  * Reads the "DNS Suffix Search List" from registry and writes the list items
    501  * whitespace separated to outptr. If the Search List is empty, the
    502  * "Primary Dns Suffix" is written to outptr.
    503  *
    504  * Returns 0 and nullifies *outptr upon inability to return the suffix list.
    505  *
    506  * Returns 1 and sets *outptr when returning a dynamically allocated string.
    507  *
    508  * Implementation supports Windows Server 2003 and newer
    509  */
    510 static ares_bool_t get_SuffixList_Windows(char **outptr)
    511 {
    512   HKEY  hKey;
    513   HKEY  hKeyEnum;
    514   char  keyName[256];
    515   DWORD keyNameBuffSize;
    516   DWORD keyIdx = 0;
    517   char *p      = NULL;
    518 
    519   *outptr = NULL;
    520 
    521   /* 1. Global DNS Suffix Search List */
    522   if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, &hKey) ==
    523       ERROR_SUCCESS) {
    524     get_REG_SZ(hKey, SEARCHLIST_KEY, outptr);
    525     if (get_REG_SZ(hKey, DOMAIN_KEY, &p)) {
    526       commajoin(outptr, p);
    527       ares_free(p);
    528       p = NULL;
    529     }
    530     RegCloseKey(hKey);
    531   }
    532 
    533   if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
    534       ERROR_SUCCESS) {
    535     if (get_REG_SZ(hKey, SEARCHLIST_KEY, &p)) {
    536       commajoin(outptr, p);
    537       ares_free(p);
    538       p = NULL;
    539     }
    540     RegCloseKey(hKey);
    541   }
    542 
    543   /* 2. Connection Specific Search List composed of:
    544    *  a. Primary DNS Suffix */
    545   if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
    546       ERROR_SUCCESS) {
    547     if (get_REG_SZ(hKey, PRIMARYDNSSUFFIX_KEY, &p)) {
    548       commajoin(outptr, p);
    549       ares_free(p);
    550       p = NULL;
    551     }
    552     RegCloseKey(hKey);
    553   }
    554 
    555   /*  b. Interface SearchList, Domain, DhcpDomain */
    556   if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
    557                     KEY_READ, &hKey) == ERROR_SUCCESS) {
    558     for (;;) {
    559       keyNameBuffSize = sizeof(keyName);
    560       if (RegEnumKeyExA(hKey, keyIdx++, keyName, &keyNameBuffSize, 0, NULL,
    561                         NULL, NULL) != ERROR_SUCCESS) {
    562         break;
    563       }
    564       if (RegOpenKeyExA(hKey, keyName, 0, KEY_QUERY_VALUE, &hKeyEnum) !=
    565           ERROR_SUCCESS) {
    566         continue;
    567       }
    568       /* p can be comma separated (SearchList) */
    569       if (get_REG_SZ(hKeyEnum, SEARCHLIST_KEY, &p)) {
    570         commajoin(outptr, p);
    571         ares_free(p);
    572         p = NULL;
    573       }
    574       if (get_REG_SZ(hKeyEnum, DOMAIN_KEY, &p)) {
    575         commajoin(outptr, p);
    576         ares_free(p);
    577         p = NULL;
    578       }
    579       if (get_REG_SZ(hKeyEnum, DHCPDOMAIN_KEY, &p)) {
    580         commajoin(outptr, p);
    581         ares_free(p);
    582         p = NULL;
    583       }
    584       RegCloseKey(hKeyEnum);
    585     }
    586     RegCloseKey(hKey);
    587   }
    588 
    589   return *outptr != NULL ? ARES_TRUE : ARES_FALSE;
    590 }
    591 
    592 ares_status_t ares_init_sysconfig_windows(const ares_channel_t *channel,
    593                                           ares_sysconfig_t     *sysconfig)
    594 {
    595   char         *line   = NULL;
    596   ares_status_t status = ARES_SUCCESS;
    597 
    598   if (get_DNS_Windows(&line)) {
    599     status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig, line,
    600                                          ARES_TRUE);
    601     ares_free(line);
    602     if (status != ARES_SUCCESS) {
    603       goto done;
    604     }
    605   }
    606 
    607   if (get_SuffixList_Windows(&line)) {
    608     sysconfig->domains = ares_strsplit(line, ", ", &sysconfig->ndomains);
    609     ares_free(line);
    610     if (sysconfig->domains == NULL) {
    611       status = ARES_EFILE;
    612     }
    613     if (status != ARES_SUCCESS) {
    614       goto done;
    615     }
    616   }
    617 
    618 done:
    619   return status;
    620 }
    621 #endif