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