• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
get_REG_SZ(HKEY hKey,const char * leafKeyName,char ** outptr)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 
commanjoin(char ** dst,const char * const src,const size_t len)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  */
commajoin(char ** dst,const char * src)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  */
compareAddresses(const void * arg1,const void * arg2)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  */
getBestRouteMetric(IF_LUID * const luid,const SOCKADDR_INET * const dest,const ULONG interfaceMetric)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 
get_DNS_Windows(char ** outptr)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  */
get_SuffixList_Windows(char ** outptr)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 
ares_init_sysconfig_windows(const ares_channel_t * channel,ares_sysconfig_t * sysconfig)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
622