1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26
27#ifdef HAVE_NETINET_IN_H
28#include <netinet/in.h>
29#endif
30#ifdef HAVE_NETINET_IN6_H
31#include <netinet/in6.h>
32#endif
33#ifdef HAVE_NETDB_H
34#include <netdb.h>
35#endif
36#ifdef HAVE_ARPA_INET_H
37#include <arpa/inet.h>
38#endif
39#ifdef __VMS
40#include <in.h>
41#include <inet.h>
42#endif
43
44#include <setjmp.h>
45#include <signal.h>
46
47#include "urldata.h"
48#include "sendf.h"
49#include "hostip.h"
50#include "hash.h"
51#include "rand.h"
52#include "share.h"
53#include "url.h"
54#include "inet_ntop.h"
55#include "inet_pton.h"
56#include "multiif.h"
57#include "doh.h"
58#include "warnless.h"
59#include "strcase.h"
60#include "easy_lock.h"
61/* The last 3 #include files should be in this order */
62#include "curl_printf.h"
63#include "curl_memory.h"
64#include "memdebug.h"
65
66#if defined(CURLRES_SYNCH) && \
67 defined(HAVE_ALARM) && \
68 defined(SIGALRM) && \
69 defined(HAVE_SIGSETJMP) && \
70 defined(GLOBAL_INIT_IS_THREADSAFE)
71/* alarm-based timeouts can only be used with all the dependencies satisfied */
72#define USE_ALARM_TIMEOUT
73#endif
74
75#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
76
77#define MAX_DNS_CACHE_SIZE 29999
78
79/*
80 * hostip.c explained
81 * ==================
82 *
83 * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
84 * source file are these:
85 *
86 * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
87 * that. The host may not be able to resolve IPv6, but we don't really have to
88 * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
89 * defined.
90 *
91 * CURLRES_ARES - is defined if libcurl is built to use c-ares for
92 * asynchronous name resolves. This can be Windows or *nix.
93 *
94 * CURLRES_THREADED - is defined if libcurl is built to run under (native)
95 * Windows, and then the name resolve will be done in a new thread, and the
96 * supported API will be the same as for ares-builds.
97 *
98 * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
99 * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
100 * defined.
101 *
102 * The host*.c sources files are split up like this:
103 *
104 * hostip.c - method-independent resolver functions and utility functions
105 * hostasyn.c - functions for asynchronous name resolves
106 * hostsyn.c - functions for synchronous name resolves
107 * hostip4.c - IPv4 specific functions
108 * hostip6.c - IPv6 specific functions
109 *
110 * The two asynchronous name resolver backends are implemented in:
111 * asyn-ares.c - functions for ares-using name resolves
112 * asyn-thread.c - functions for threaded name resolves
113
114 * The hostip.h is the united header file for all this. It defines the
115 * CURLRES_* defines based on the config*.h and curl_setup.h defines.
116 */
117
118static void freednsentry(void *freethis);
119
120/*
121 * Curl_printable_address() stores a printable version of the 1st address
122 * given in the 'ai' argument. The result will be stored in the buf that is
123 * bufsize bytes big.
124 *
125 * If the conversion fails, the target buffer is empty.
126 */
127void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf,
128 size_t bufsize)
129{
130 DEBUGASSERT(bufsize);
131 buf[0] = 0;
132
133 switch(ai->ai_family) {
134 case AF_INET: {
135 const struct sockaddr_in *sa4 = (const void *)ai->ai_addr;
136 const struct in_addr *ipaddr4 = &sa4->sin_addr;
137 (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize);
138 break;
139 }
140#ifdef ENABLE_IPV6
141 case AF_INET6: {
142 const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr;
143 const struct in6_addr *ipaddr6 = &sa6->sin6_addr;
144 (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, bufsize);
145 break;
146 }
147#endif
148 default:
149 break;
150 }
151}
152
153/*
154 * Create a hostcache id string for the provided host + port, to be used by
155 * the DNS caching. Without alloc. Return length of the id string.
156 */
157static size_t
158create_hostcache_id(const char *name,
159 size_t nlen, /* 0 or actual name length */
160 int port, char *ptr, size_t buflen)
161{
162 size_t len = nlen ? nlen : strlen(s: name);
163 size_t olen = 0;
164 DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
165 if(len > (buflen - 7))
166 len = buflen - 7;
167 /* store and lower case the name */
168 while(len--) {
169 *ptr++ = Curl_raw_tolower(in: *name++);
170 olen++;
171 }
172 olen += msnprintf(buffer: ptr, maxlength: 7, format: ":%u", port);
173 return olen;
174}
175
176struct hostcache_prune_data {
177 time_t now;
178 time_t oldest; /* oldest time in cache not pruned. */
179 int cache_timeout;
180};
181
182/*
183 * This function is set as a callback to be called for every entry in the DNS
184 * cache when we want to prune old unused entries.
185 *
186 * Returning non-zero means remove the entry, return 0 to keep it in the
187 * cache.
188 */
189static int
190hostcache_timestamp_remove(void *datap, void *hc)
191{
192 struct hostcache_prune_data *prune =
193 (struct hostcache_prune_data *) datap;
194 struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
195
196 if(c->timestamp) {
197 /* age in seconds */
198 time_t age = prune->now - c->timestamp;
199 if(age >= prune->cache_timeout)
200 return TRUE;
201 if(age > prune->oldest)
202 prune->oldest = age;
203 }
204 return FALSE;
205}
206
207/*
208 * Prune the DNS cache. This assumes that a lock has already been taken.
209 * Returns the 'age' of the oldest still kept entry.
210 */
211static time_t
212hostcache_prune(struct Curl_hash *hostcache, int cache_timeout,
213 time_t now)
214{
215 struct hostcache_prune_data user;
216
217 user.cache_timeout = cache_timeout;
218 user.now = now;
219 user.oldest = 0;
220
221 Curl_hash_clean_with_criterium(h: hostcache,
222 user: (void *) &user,
223 comp: hostcache_timestamp_remove);
224
225 return user.oldest;
226}
227
228/*
229 * Library-wide function for pruning the DNS cache. This function takes and
230 * returns the appropriate locks.
231 */
232void Curl_hostcache_prune(struct Curl_easy *data)
233{
234 time_t now;
235 /* the timeout may be set -1 (forever) */
236 int timeout = data->set.dns_cache_timeout;
237
238 if(!data->dns.hostcache)
239 /* NULL hostcache means we can't do it */
240 return;
241
242 if(data->share)
243 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
244
245 time(timer: &now);
246
247 do {
248 /* Remove outdated and unused entries from the hostcache */
249 time_t oldest = hostcache_prune(hostcache: data->dns.hostcache, cache_timeout: timeout, now);
250
251 if(oldest < INT_MAX)
252 timeout = (int)oldest; /* we know it fits */
253 else
254 timeout = INT_MAX - 1;
255
256 /* if the cache size is still too big, use the oldest age as new
257 prune limit */
258 } while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE));
259
260 if(data->share)
261 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
262}
263
264#ifdef USE_ALARM_TIMEOUT
265/* Beware this is a global and unique instance. This is used to store the
266 return address that we can jump back to from inside a signal handler. This
267 is not thread-safe stuff. */
268static sigjmp_buf curl_jmpenv;
269static curl_simple_lock curl_jmpenv_lock;
270#endif
271
272/* lookup address, returns entry if found and not stale */
273static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
274 const char *hostname,
275 int port)
276{
277 struct Curl_dns_entry *dns = NULL;
278 char entry_id[MAX_HOSTCACHE_LEN];
279
280 /* Create an entry id, based upon the hostname and port */
281 size_t entry_len = create_hostcache_id(name: hostname, nlen: 0, port,
282 ptr: entry_id, buflen: sizeof(entry_id));
283
284 /* See if its already in our dns cache */
285 dns = Curl_hash_pick(data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
286
287 /* No entry found in cache, check if we might have a wildcard entry */
288 if(!dns && data->state.wildcard_resolve) {
289 entry_len = create_hostcache_id(name: "*", nlen: 1, port, ptr: entry_id, buflen: sizeof(entry_id));
290
291 /* See if it's already in our dns cache */
292 dns = Curl_hash_pick(data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
293 }
294
295 if(dns && (data->set.dns_cache_timeout != -1)) {
296 /* See whether the returned entry is stale. Done before we release lock */
297 struct hostcache_prune_data user;
298
299 time(timer: &user.now);
300 user.cache_timeout = data->set.dns_cache_timeout;
301 user.oldest = 0;
302
303 if(hostcache_timestamp_remove(datap: &user, hc: dns)) {
304 infof(data, "Hostname in DNS cache was stale, zapped");
305 dns = NULL; /* the memory deallocation is being handled by the hash */
306 Curl_hash_delete(h: data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
307 }
308 }
309
310 /* See if the returned entry matches the required resolve mode */
311 if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) {
312 int pf = PF_INET;
313 bool found = false;
314 struct Curl_addrinfo *addr = dns->addr;
315
316#ifdef PF_INET6
317 if(data->conn->ip_version == CURL_IPRESOLVE_V6)
318 pf = PF_INET6;
319#endif
320
321 while(addr) {
322 if(addr->ai_family == pf) {
323 found = true;
324 break;
325 }
326 addr = addr->ai_next;
327 }
328
329 if(!found) {
330 infof(data, "Hostname in DNS cache doesn't have needed family, zapped");
331 dns = NULL; /* the memory deallocation is being handled by the hash */
332 Curl_hash_delete(h: data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
333 }
334 }
335 return dns;
336}
337
338/*
339 * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
340 *
341 * Curl_resolv() checks initially and multi_runsingle() checks each time
342 * it discovers the handle in the state WAITRESOLVE whether the hostname
343 * has already been resolved and the address has already been stored in
344 * the DNS cache. This short circuits waiting for a lot of pending
345 * lookups for the same hostname requested by different handles.
346 *
347 * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
348 *
349 * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
350 * use, or we'll leak memory!
351 */
352struct Curl_dns_entry *
353Curl_fetch_addr(struct Curl_easy *data,
354 const char *hostname,
355 int port)
356{
357 struct Curl_dns_entry *dns = NULL;
358
359 if(data->share)
360 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
361
362 dns = fetch_addr(data, hostname, port);
363
364 if(dns)
365 dns->inuse++; /* we use it! */
366
367 if(data->share)
368 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
369
370 return dns;
371}
372
373#ifndef CURL_DISABLE_SHUFFLE_DNS
374/*
375 * Return # of addresses in a Curl_addrinfo struct
376 */
377static int num_addresses(const struct Curl_addrinfo *addr)
378{
379 int i = 0;
380 while(addr) {
381 addr = addr->ai_next;
382 i++;
383 }
384 return i;
385}
386
387UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
388 struct Curl_addrinfo **addr);
389/*
390 * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
391 * struct by re-linking its linked list.
392 *
393 * The addr argument should be the address of a pointer to the head node of a
394 * `Curl_addrinfo` list and it will be modified to point to the new head after
395 * shuffling.
396 *
397 * Not declared static only to make it easy to use in a unit test!
398 *
399 * @unittest: 1608
400 */
401UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
402 struct Curl_addrinfo **addr)
403{
404 CURLcode result = CURLE_OK;
405 const int num_addrs = num_addresses(addr: *addr);
406
407 if(num_addrs > 1) {
408 struct Curl_addrinfo **nodes;
409 infof(data, "Shuffling %i addresses", num_addrs);
410
411 nodes = malloc(num_addrs*sizeof(*nodes));
412 if(nodes) {
413 int i;
414 unsigned int *rnd;
415 const size_t rnd_size = num_addrs * sizeof(*rnd);
416
417 /* build a plain array of Curl_addrinfo pointers */
418 nodes[0] = *addr;
419 for(i = 1; i < num_addrs; i++) {
420 nodes[i] = nodes[i-1]->ai_next;
421 }
422
423 rnd = malloc(rnd_size);
424 if(rnd) {
425 /* Fisher-Yates shuffle */
426 if(Curl_rand(data, rnd: (unsigned char *)rnd, num: rnd_size) == CURLE_OK) {
427 struct Curl_addrinfo *swap_tmp;
428 for(i = num_addrs - 1; i > 0; i--) {
429 swap_tmp = nodes[rnd[i] % (i + 1)];
430 nodes[rnd[i] % (i + 1)] = nodes[i];
431 nodes[i] = swap_tmp;
432 }
433
434 /* relink list in the new order */
435 for(i = 1; i < num_addrs; i++) {
436 nodes[i-1]->ai_next = nodes[i];
437 }
438
439 nodes[num_addrs-1]->ai_next = NULL;
440 *addr = nodes[0];
441 }
442 free(rnd);
443 }
444 else
445 result = CURLE_OUT_OF_MEMORY;
446 free(nodes);
447 }
448 else
449 result = CURLE_OUT_OF_MEMORY;
450 }
451 return result;
452}
453#endif
454
455/*
456 * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
457 *
458 * When calling Curl_resolv() has resulted in a response with a returned
459 * address, we call this function to store the information in the dns
460 * cache etc
461 *
462 * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
463 */
464struct Curl_dns_entry *
465Curl_cache_addr(struct Curl_easy *data,
466 struct Curl_addrinfo *addr,
467 const char *hostname,
468 size_t hostlen, /* length or zero */
469 int port)
470{
471 char entry_id[MAX_HOSTCACHE_LEN];
472 size_t entry_len;
473 struct Curl_dns_entry *dns;
474 struct Curl_dns_entry *dns2;
475
476#ifndef CURL_DISABLE_SHUFFLE_DNS
477 /* shuffle addresses if requested */
478 if(data->set.dns_shuffle_addresses) {
479 CURLcode result = Curl_shuffle_addr(data, addr: &addr);
480 if(result)
481 return NULL;
482 }
483#endif
484
485 /* Create a new cache entry */
486 dns = calloc(1, sizeof(struct Curl_dns_entry));
487 if(!dns) {
488 return NULL;
489 }
490
491 /* Create an entry id, based upon the hostname and port */
492 entry_len = create_hostcache_id(name: hostname, nlen: hostlen, port,
493 ptr: entry_id, buflen: sizeof(entry_id));
494
495 dns->inuse = 1; /* the cache has the first reference */
496 dns->addr = addr; /* this is the address(es) */
497 time(timer: &dns->timestamp);
498 if(dns->timestamp == 0)
499 dns->timestamp = 1; /* zero indicates permanent CURLOPT_RESOLVE entry */
500
501 /* Store the resolved data in our DNS cache. */
502 dns2 = Curl_hash_add(h: data->dns.hostcache, key: entry_id, key_len: entry_len + 1,
503 p: (void *)dns);
504 if(!dns2) {
505 free(dns);
506 return NULL;
507 }
508
509 dns = dns2;
510 dns->inuse++; /* mark entry as in-use */
511 return dns;
512}
513
514#ifdef ENABLE_IPV6
515/* return a static IPv6 ::1 for the name */
516static struct Curl_addrinfo *get_localhost6(int port, const char *name)
517{
518 struct Curl_addrinfo *ca;
519 const size_t ss_size = sizeof(struct sockaddr_in6);
520 const size_t hostlen = strlen(s: name);
521 struct sockaddr_in6 sa6;
522 unsigned char ipv6[16];
523 unsigned short port16 = (unsigned short)(port & 0xffff);
524 ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1);
525 if(!ca)
526 return NULL;
527
528 sa6.sin6_family = AF_INET6;
529 sa6.sin6_port = htons(port16);
530 sa6.sin6_flowinfo = 0;
531 sa6.sin6_scope_id = 0;
532 if(Curl_inet_pton(AF_INET6, "::1", ipv6) < 1)
533 return NULL;
534 memcpy(dest: &sa6.sin6_addr, src: ipv6, n: sizeof(ipv6));
535
536 ca->ai_flags = 0;
537 ca->ai_family = AF_INET6;
538 ca->ai_socktype = SOCK_STREAM;
539 ca->ai_protocol = IPPROTO_TCP;
540 ca->ai_addrlen = (curl_socklen_t)ss_size;
541 ca->ai_next = NULL;
542 ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
543 memcpy(dest: ca->ai_addr, src: &sa6, n: ss_size);
544 ca->ai_canonname = (char *)ca->ai_addr + ss_size;
545 strcpy(dest: ca->ai_canonname, src: name);
546 return ca;
547}
548#else
549#define get_localhost6(x,y) NULL
550#endif
551
552/* return a static IPv4 127.0.0.1 for the given name */
553static struct Curl_addrinfo *get_localhost(int port, const char *name)
554{
555 struct Curl_addrinfo *ca;
556 struct Curl_addrinfo *ca6;
557 const size_t ss_size = sizeof(struct sockaddr_in);
558 const size_t hostlen = strlen(s: name);
559 struct sockaddr_in sa;
560 unsigned int ipv4;
561 unsigned short port16 = (unsigned short)(port & 0xffff);
562
563 /* memset to clear the sa.sin_zero field */
564 memset(s: &sa, c: 0, n: sizeof(sa));
565 sa.sin_family = AF_INET;
566 sa.sin_port = htons(port16);
567 if(Curl_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1)
568 return NULL;
569 memcpy(dest: &sa.sin_addr, src: &ipv4, n: sizeof(ipv4));
570
571 ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1);
572 if(!ca)
573 return NULL;
574 ca->ai_flags = 0;
575 ca->ai_family = AF_INET;
576 ca->ai_socktype = SOCK_STREAM;
577 ca->ai_protocol = IPPROTO_TCP;
578 ca->ai_addrlen = (curl_socklen_t)ss_size;
579 ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
580 memcpy(dest: ca->ai_addr, src: &sa, n: ss_size);
581 ca->ai_canonname = (char *)ca->ai_addr + ss_size;
582 strcpy(dest: ca->ai_canonname, src: name);
583
584 ca6 = get_localhost6(port, name);
585 if(!ca6)
586 return ca;
587 ca6->ai_next = ca;
588 return ca6;
589}
590
591#ifdef ENABLE_IPV6
592/*
593 * Curl_ipv6works() returns TRUE if IPv6 seems to work.
594 */
595bool Curl_ipv6works(struct Curl_easy *data)
596{
597 if(data) {
598 /* the nature of most system is that IPv6 status doesn't come and go
599 during a program's lifetime so we only probe the first time and then we
600 have the info kept for fast reuse */
601 DEBUGASSERT(data);
602 DEBUGASSERT(data->multi);
603 if(data->multi->ipv6_up == IPV6_UNKNOWN) {
604 bool works = Curl_ipv6works(NULL);
605 data->multi->ipv6_up = works ? IPV6_WORKS : IPV6_DEAD;
606 }
607 return data->multi->ipv6_up == IPV6_WORKS;
608 }
609 else {
610 int ipv6_works = -1;
611 /* probe to see if we have a working IPv6 stack */
612 curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, protocol: 0);
613 if(s == CURL_SOCKET_BAD)
614 /* an IPv6 address was requested but we can't get/use one */
615 ipv6_works = 0;
616 else {
617 ipv6_works = 1;
618 sclose(s);
619 }
620 return (ipv6_works>0)?TRUE:FALSE;
621 }
622}
623#endif /* ENABLE_IPV6 */
624
625/*
626 * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4
627 * (or IPv6 if supported) address.
628 */
629bool Curl_host_is_ipnum(const char *hostname)
630{
631 struct in_addr in;
632#ifdef ENABLE_IPV6
633 struct in6_addr in6;
634#endif
635 if(Curl_inet_pton(AF_INET, hostname, &in) > 0
636#ifdef ENABLE_IPV6
637 || Curl_inet_pton(AF_INET6, hostname, &in6) > 0
638#endif
639 )
640 return TRUE;
641 return FALSE;
642}
643
644
645/* return TRUE if 'part' is a case insensitive tail of 'full' */
646static bool tailmatch(const char *full, const char *part)
647{
648 size_t plen = strlen(s: part);
649 size_t flen = strlen(s: full);
650 if(plen > flen)
651 return FALSE;
652 return strncasecompare(part, &full[flen - plen], plen);
653}
654
655/*
656 * Curl_resolv() is the main name resolve function within libcurl. It resolves
657 * a name and returns a pointer to the entry in the 'entry' argument (if one
658 * is provided). This function might return immediately if we're using asynch
659 * resolves. See the return codes.
660 *
661 * The cache entry we return will get its 'inuse' counter increased when this
662 * function is used. You MUST call Curl_resolv_unlock() later (when you're
663 * done using this struct) to decrease the counter again.
664 *
665 * Return codes:
666 *
667 * CURLRESOLV_ERROR (-1) = error, no pointer
668 * CURLRESOLV_RESOLVED (0) = OK, pointer provided
669 * CURLRESOLV_PENDING (1) = waiting for response, no pointer
670 */
671
672enum resolve_t Curl_resolv(struct Curl_easy *data,
673 const char *hostname,
674 int port,
675 bool allowDOH,
676 struct Curl_dns_entry **entry)
677{
678 struct Curl_dns_entry *dns = NULL;
679 CURLcode result;
680 enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */
681 struct connectdata *conn = data->conn;
682 /* We should intentionally error and not resolve .onion TLDs */
683 size_t hostname_len = strlen(s: hostname);
684 if(hostname_len >= 7 &&
685 (curl_strequal(s1: &hostname[hostname_len - 6], s2: ".onion") ||
686 curl_strequal(s1: &hostname[hostname_len - 7], s2: ".onion."))) {
687 failf(data, fmt: "Not resolving .onion address (RFC 7686)");
688 return CURLRESOLV_ERROR;
689 }
690 *entry = NULL;
691#ifndef CURL_DISABLE_DOH
692 conn->bits.doh = FALSE; /* default is not */
693#else
694 (void)allowDOH;
695#endif
696
697 if(data->share)
698 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
699
700 dns = fetch_addr(data, hostname, port);
701
702 if(dns) {
703 infof(data, "Hostname %s was found in DNS cache", hostname);
704 dns->inuse++; /* we use it! */
705 rc = CURLRESOLV_RESOLVED;
706 }
707
708 if(data->share)
709 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
710
711 if(!dns) {
712 /* The entry was not in the cache. Resolve it to IP address */
713
714 struct Curl_addrinfo *addr = NULL;
715 int respwait = 0;
716#if !defined(CURL_DISABLE_DOH) || !defined(USE_RESOLVE_ON_IPS)
717 struct in_addr in;
718#endif
719#ifndef CURL_DISABLE_DOH
720#ifndef USE_RESOLVE_ON_IPS
721 const
722#endif
723 bool ipnum = FALSE;
724#endif
725
726 /* notify the resolver start callback */
727 if(data->set.resolver_start) {
728 int st;
729 Curl_set_in_callback(data, true);
730 st = data->set.resolver_start(
731#ifdef USE_CURL_ASYNC
732 data->state.async.resolver,
733#else
734 NULL,
735#endif
736 NULL,
737 data->set.resolver_start_client);
738 Curl_set_in_callback(data, false);
739 if(st)
740 return CURLRESOLV_ERROR;
741 }
742
743#ifndef USE_RESOLVE_ON_IPS
744 /* First check if this is an IPv4 address string */
745 if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
746 /* This is a dotted IP address 123.123.123.123-style */
747 addr = Curl_ip2addr(AF_INET, inaddr: &in, hostname, port);
748#ifdef ENABLE_IPV6
749 if(!addr) {
750 struct in6_addr in6;
751 /* check if this is an IPv6 address string */
752 if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
753 /* This is an IPv6 address literal */
754 addr = Curl_ip2addr(AF_INET6, inaddr: &in6, hostname, port);
755 }
756#endif /* ENABLE_IPV6 */
757
758#else /* if USE_RESOLVE_ON_IPS */
759#ifndef CURL_DISABLE_DOH
760 /* First check if this is an IPv4 address string */
761 if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
762 /* This is a dotted IP address 123.123.123.123-style */
763 ipnum = TRUE;
764#ifdef ENABLE_IPV6
765 else {
766 struct in6_addr in6;
767 /* check if this is an IPv6 address string */
768 if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
769 /* This is an IPv6 address literal */
770 ipnum = TRUE;
771 }
772#endif /* ENABLE_IPV6 */
773#endif /* CURL_DISABLE_DOH */
774
775#endif /* !USE_RESOLVE_ON_IPS */
776
777 if(!addr) {
778 if(conn->ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data))
779 return CURLRESOLV_ERROR;
780
781 if(strcasecompare(hostname, "localhost") ||
782 tailmatch(full: hostname, part: ".localhost"))
783 addr = get_localhost(port, name: hostname);
784#ifndef CURL_DISABLE_DOH
785 else if(allowDOH && data->set.doh && !ipnum)
786 addr = Curl_doh(data, hostname, port, waitp: &respwait);
787#endif
788 else {
789 /* Check what IP specifics the app has requested and if we can provide
790 * it. If not, bail out. */
791 if(!Curl_ipvalid(data, conn))
792 return CURLRESOLV_ERROR;
793 /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
794 non-zero value indicating that we need to wait for the response to
795 the resolve call */
796 addr = Curl_getaddrinfo(data, hostname, port, waitp: &respwait);
797 }
798 }
799 if(!addr) {
800 if(respwait) {
801 /* the response to our resolve call will come asynchronously at
802 a later time, good or bad */
803 /* First, check that we haven't received the info by now */
804 result = Curl_resolv_check(data, dns: &dns);
805 if(result) /* error detected */
806 return CURLRESOLV_ERROR;
807 if(dns)
808 rc = CURLRESOLV_RESOLVED; /* pointer provided */
809 else
810 rc = CURLRESOLV_PENDING; /* no info yet */
811 }
812 }
813 else {
814 if(data->share)
815 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
816
817 /* we got a response, store it in the cache */
818 dns = Curl_cache_addr(data, addr, hostname, hostlen: 0, port);
819
820 if(data->share)
821 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
822
823 if(!dns)
824 /* returned failure, bail out nicely */
825 Curl_freeaddrinfo(cahead: addr);
826 else
827 rc = CURLRESOLV_RESOLVED;
828 }
829 }
830
831 *entry = dns;
832
833 return rc;
834}
835
836#ifdef USE_ALARM_TIMEOUT
837/*
838 * This signal handler jumps back into the main libcurl code and continues
839 * execution. This effectively causes the remainder of the application to run
840 * within a signal handler which is nonportable and could lead to problems.
841 */
842static
843void alarmfunc(int sig)
844{
845 (void)sig;
846 siglongjmp(curl_jmpenv, 1);
847}
848#endif /* USE_ALARM_TIMEOUT */
849
850/*
851 * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
852 * timeout. This function might return immediately if we're using asynch
853 * resolves. See the return codes.
854 *
855 * The cache entry we return will get its 'inuse' counter increased when this
856 * function is used. You MUST call Curl_resolv_unlock() later (when you're
857 * done using this struct) to decrease the counter again.
858 *
859 * If built with a synchronous resolver and use of signals is not
860 * disabled by the application, then a nonzero timeout will cause a
861 * timeout after the specified number of milliseconds. Otherwise, timeout
862 * is ignored.
863 *
864 * Return codes:
865 *
866 * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
867 * CURLRESOLV_ERROR (-1) = error, no pointer
868 * CURLRESOLV_RESOLVED (0) = OK, pointer provided
869 * CURLRESOLV_PENDING (1) = waiting for response, no pointer
870 */
871
872enum resolve_t Curl_resolv_timeout(struct Curl_easy *data,
873 const char *hostname,
874 int port,
875 struct Curl_dns_entry **entry,
876 timediff_t timeoutms)
877{
878#ifdef USE_ALARM_TIMEOUT
879#ifdef HAVE_SIGACTION
880 struct sigaction keep_sigact; /* store the old struct here */
881 volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
882 struct sigaction sigact;
883#else
884#ifdef HAVE_SIGNAL
885 void (*keep_sigact)(int); /* store the old handler here */
886#endif /* HAVE_SIGNAL */
887#endif /* HAVE_SIGACTION */
888 volatile long timeout;
889 volatile unsigned int prev_alarm = 0;
890#endif /* USE_ALARM_TIMEOUT */
891 enum resolve_t rc;
892
893 *entry = NULL;
894
895 if(timeoutms < 0)
896 /* got an already expired timeout */
897 return CURLRESOLV_TIMEDOUT;
898
899#ifdef USE_ALARM_TIMEOUT
900 if(data->set.no_signal)
901 /* Ignore the timeout when signals are disabled */
902 timeout = 0;
903 else
904 timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
905
906 if(!timeout)
907 /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
908 return Curl_resolv(data, hostname, port, TRUE, entry);
909
910 if(timeout < 1000) {
911 /* The alarm() function only provides integer second resolution, so if
912 we want to wait less than one second we must bail out already now. */
913 failf(data,
914 "remaining timeout of %ld too small to resolve via SIGALRM method",
915 timeout);
916 return CURLRESOLV_TIMEDOUT;
917 }
918 /* This allows us to time-out from the name resolver, as the timeout
919 will generate a signal and we will siglongjmp() from that here.
920 This technique has problems (see alarmfunc).
921 This should be the last thing we do before calling Curl_resolv(),
922 as otherwise we'd have to worry about variables that get modified
923 before we invoke Curl_resolv() (and thus use "volatile"). */
924 curl_simple_lock_lock(&curl_jmpenv_lock);
925
926 if(sigsetjmp(curl_jmpenv, 1)) {
927 /* this is coming from a siglongjmp() after an alarm signal */
928 failf(data, "name lookup timed out");
929 rc = CURLRESOLV_ERROR;
930 goto clean_up;
931 }
932 else {
933 /*************************************************************
934 * Set signal handler to catch SIGALRM
935 * Store the old value to be able to set it back later!
936 *************************************************************/
937#ifdef HAVE_SIGACTION
938 sigaction(SIGALRM, NULL, &sigact);
939 keep_sigact = sigact;
940 keep_copysig = TRUE; /* yes, we have a copy */
941 sigact.sa_handler = alarmfunc;
942#ifdef SA_RESTART
943 /* HPUX doesn't have SA_RESTART but defaults to that behavior! */
944 sigact.sa_flags &= ~SA_RESTART;
945#endif
946 /* now set the new struct */
947 sigaction(SIGALRM, &sigact, NULL);
948#else /* HAVE_SIGACTION */
949 /* no sigaction(), revert to the much lamer signal() */
950#ifdef HAVE_SIGNAL
951 keep_sigact = signal(SIGALRM, alarmfunc);
952#endif
953#endif /* HAVE_SIGACTION */
954
955 /* alarm() makes a signal get sent when the timeout fires off, and that
956 will abort system calls */
957 prev_alarm = alarm(curlx_sltoui(timeout/1000L));
958 }
959
960#else
961#ifndef CURLRES_ASYNCH
962 if(timeoutms)
963 infof(data, "timeout on name lookup is not supported");
964#else
965 (void)timeoutms; /* timeoutms not used with an async resolver */
966#endif
967#endif /* USE_ALARM_TIMEOUT */
968
969 /* Perform the actual name resolution. This might be interrupted by an
970 * alarm if it takes too long.
971 */
972 rc = Curl_resolv(data, hostname, port, TRUE, entry);
973
974#ifdef USE_ALARM_TIMEOUT
975clean_up:
976
977 if(!prev_alarm)
978 /* deactivate a possibly active alarm before uninstalling the handler */
979 alarm(0);
980
981#ifdef HAVE_SIGACTION
982 if(keep_copysig) {
983 /* we got a struct as it looked before, now put that one back nice
984 and clean */
985 sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
986 }
987#else
988#ifdef HAVE_SIGNAL
989 /* restore the previous SIGALRM handler */
990 signal(SIGALRM, keep_sigact);
991#endif
992#endif /* HAVE_SIGACTION */
993
994 curl_simple_lock_unlock(&curl_jmpenv_lock);
995
996 /* switch back the alarm() to either zero or to what it was before minus
997 the time we spent until now! */
998 if(prev_alarm) {
999 /* there was an alarm() set before us, now put it back */
1000 timediff_t elapsed_secs = Curl_timediff(Curl_now(),
1001 data->conn->created) / 1000;
1002
1003 /* the alarm period is counted in even number of seconds */
1004 unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs);
1005
1006 if(!alarm_set ||
1007 ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
1008 /* if the alarm time-left reached zero or turned "negative" (counted
1009 with unsigned values), we should fire off a SIGALRM here, but we
1010 won't, and zero would be to switch it off so we never set it to
1011 less than 1! */
1012 alarm(1);
1013 rc = CURLRESOLV_TIMEDOUT;
1014 failf(data, "Previous alarm fired off");
1015 }
1016 else
1017 alarm((unsigned int)alarm_set);
1018 }
1019#endif /* USE_ALARM_TIMEOUT */
1020
1021 return rc;
1022}
1023
1024/*
1025 * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
1026 * made, the struct may be destroyed due to pruning. It is important that only
1027 * one unlock is made for each Curl_resolv() call.
1028 *
1029 * May be called with 'data' == NULL for global cache.
1030 */
1031void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns)
1032{
1033 if(data && data->share)
1034 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1035
1036 freednsentry(freethis: dns);
1037
1038 if(data && data->share)
1039 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1040}
1041
1042/*
1043 * File-internal: release cache dns entry reference, free if inuse drops to 0
1044 */
1045static void freednsentry(void *freethis)
1046{
1047 struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
1048 DEBUGASSERT(dns && (dns->inuse>0));
1049
1050 dns->inuse--;
1051 if(dns->inuse == 0) {
1052 Curl_freeaddrinfo(cahead: dns->addr);
1053 free(dns);
1054 }
1055}
1056
1057/*
1058 * Curl_init_dnscache() inits a new DNS cache.
1059 */
1060void Curl_init_dnscache(struct Curl_hash *hash, int size)
1061{
1062 Curl_hash_init(h: hash, slots: size, hfunc: Curl_hash_str, comparator: Curl_str_key_compare,
1063 dtor: freednsentry);
1064}
1065
1066/*
1067 * Curl_hostcache_clean()
1068 *
1069 * This _can_ be called with 'data' == NULL but then of course no locking
1070 * can be done!
1071 */
1072
1073void Curl_hostcache_clean(struct Curl_easy *data,
1074 struct Curl_hash *hash)
1075{
1076 if(data && data->share)
1077 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1078
1079 Curl_hash_clean(h: hash);
1080
1081 if(data && data->share)
1082 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1083}
1084
1085
1086CURLcode Curl_loadhostpairs(struct Curl_easy *data)
1087{
1088 struct curl_slist *hostp;
1089 char *host_end;
1090
1091 /* Default is no wildcard found */
1092 data->state.wildcard_resolve = false;
1093
1094 for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
1095 char entry_id[MAX_HOSTCACHE_LEN];
1096 if(!hostp->data)
1097 continue;
1098 if(hostp->data[0] == '-') {
1099 unsigned long num = 0;
1100 size_t entry_len;
1101 size_t hlen = 0;
1102 host_end = strchr(s: &hostp->data[1], c: ':');
1103
1104 if(host_end) {
1105 hlen = host_end - &hostp->data[1];
1106 num = strtoul(nptr: ++host_end, NULL, base: 10);
1107 if(!hlen || (num > 0xffff))
1108 host_end = NULL;
1109 }
1110 if(!host_end) {
1111 infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'",
1112 hostp->data);
1113 continue;
1114 }
1115 /* Create an entry id, based upon the hostname and port */
1116 entry_len = create_hostcache_id(name: &hostp->data[1], nlen: hlen, port: (int)num,
1117 ptr: entry_id, buflen: sizeof(entry_id));
1118 if(data->share)
1119 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1120
1121 /* delete entry, ignore if it didn't exist */
1122 Curl_hash_delete(h: data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
1123
1124 if(data->share)
1125 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1126 }
1127 else {
1128 struct Curl_dns_entry *dns;
1129 struct Curl_addrinfo *head = NULL, *tail = NULL;
1130 size_t entry_len;
1131 char address[64];
1132#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
1133 char *addresses = NULL;
1134#endif
1135 char *addr_begin;
1136 char *addr_end;
1137 char *port_ptr;
1138 int port = 0;
1139 char *end_ptr;
1140 bool permanent = TRUE;
1141 unsigned long tmp_port;
1142 bool error = true;
1143 char *host_begin = hostp->data;
1144 size_t hlen = 0;
1145
1146 if(host_begin[0] == '+') {
1147 host_begin++;
1148 permanent = FALSE;
1149 }
1150 host_end = strchr(s: host_begin, c: ':');
1151 if(!host_end)
1152 goto err;
1153 hlen = host_end - host_begin;
1154
1155 port_ptr = host_end + 1;
1156 tmp_port = strtoul(nptr: port_ptr, endptr: &end_ptr, base: 10);
1157 if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':')
1158 goto err;
1159
1160 port = (int)tmp_port;
1161#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
1162 addresses = end_ptr + 1;
1163#endif
1164
1165 while(*end_ptr) {
1166 size_t alen;
1167 struct Curl_addrinfo *ai;
1168
1169 addr_begin = end_ptr + 1;
1170 addr_end = strchr(s: addr_begin, c: ',');
1171 if(!addr_end)
1172 addr_end = addr_begin + strlen(s: addr_begin);
1173 end_ptr = addr_end;
1174
1175 /* allow IP(v6) address within [brackets] */
1176 if(*addr_begin == '[') {
1177 if(addr_end == addr_begin || *(addr_end - 1) != ']')
1178 goto err;
1179 ++addr_begin;
1180 --addr_end;
1181 }
1182
1183 alen = addr_end - addr_begin;
1184 if(!alen)
1185 continue;
1186
1187 if(alen >= sizeof(address))
1188 goto err;
1189
1190 memcpy(dest: address, src: addr_begin, n: alen);
1191 address[alen] = '\0';
1192
1193#ifndef ENABLE_IPV6
1194 if(strchr(address, ':')) {
1195 infof(data, "Ignoring resolve address '%s', missing IPv6 support.",
1196 address);
1197 continue;
1198 }
1199#endif
1200
1201 ai = Curl_str2addr(dotted: address, port);
1202 if(!ai) {
1203 infof(data, "Resolve address '%s' found illegal", address);
1204 goto err;
1205 }
1206
1207 if(tail) {
1208 tail->ai_next = ai;
1209 tail = tail->ai_next;
1210 }
1211 else {
1212 head = tail = ai;
1213 }
1214 }
1215
1216 if(!head)
1217 goto err;
1218
1219 error = false;
1220err:
1221 if(error) {
1222 failf(data, fmt: "Couldn't parse CURLOPT_RESOLVE entry '%s'",
1223 hostp->data);
1224 Curl_freeaddrinfo(cahead: head);
1225 return CURLE_SETOPT_OPTION_SYNTAX;
1226 }
1227
1228 /* Create an entry id, based upon the hostname and port */
1229 entry_len = create_hostcache_id(name: host_begin, nlen: hlen, port,
1230 ptr: entry_id, buflen: sizeof(entry_id));
1231
1232 if(data->share)
1233 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1234
1235 /* See if it's already in our dns cache */
1236 dns = Curl_hash_pick(data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
1237
1238 if(dns) {
1239 infof(data, "RESOLVE %.*s:%d - old addresses discarded",
1240 (int)hlen, host_begin, port);
1241 /* delete old entry, there are two reasons for this
1242 1. old entry may have different addresses.
1243 2. even if entry with correct addresses is already in the cache,
1244 but if it is close to expire, then by the time next http
1245 request is made, it can get expired and pruned because old
1246 entry is not necessarily marked as permanent.
1247 3. when adding a non-permanent entry, we want it to remove and
1248 replace an existing permanent entry.
1249 4. when adding a non-permanent entry, we want it to get a "fresh"
1250 timeout that starts _now_. */
1251
1252 Curl_hash_delete(h: data->dns.hostcache, key: entry_id, key_len: entry_len + 1);
1253 }
1254
1255 /* put this new host in the cache */
1256 dns = Curl_cache_addr(data, addr: head, hostname: host_begin, hostlen: hlen, port);
1257 if(dns) {
1258 if(permanent)
1259 dns->timestamp = 0; /* mark as permanent */
1260 /* release the returned reference; the cache itself will keep the
1261 * entry alive: */
1262 dns->inuse--;
1263 }
1264
1265 if(data->share)
1266 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1267
1268 if(!dns) {
1269 Curl_freeaddrinfo(cahead: head);
1270 return CURLE_OUT_OF_MEMORY;
1271 }
1272 infof(data, "Added %.*s:%d:%s to DNS cache%s",
1273 (int)hlen, host_begin, port, addresses,
1274 permanent ? "" : " (non-permanent)");
1275
1276 /* Wildcard hostname */
1277 if((hlen == 1) && (host_begin[0] == '*')) {
1278 infof(data, "RESOLVE *:%d using wildcard", port);
1279 data->state.wildcard_resolve = true;
1280 }
1281 }
1282 }
1283 data->state.resolve = NULL; /* dealt with now */
1284
1285 return CURLE_OK;
1286}
1287
1288CURLcode Curl_resolv_check(struct Curl_easy *data,
1289 struct Curl_dns_entry **dns)
1290{
1291#if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH)
1292 (void)data;
1293 (void)dns;
1294#endif
1295#ifndef CURL_DISABLE_DOH
1296 if(data->conn->bits.doh)
1297 return Curl_doh_is_resolved(data, dns);
1298#endif
1299 return Curl_resolver_is_resolved(data, dns);
1300}
1301
1302int Curl_resolv_getsock(struct Curl_easy *data,
1303 curl_socket_t *socks)
1304{
1305#ifdef CURLRES_ASYNCH
1306#ifndef CURL_DISABLE_DOH
1307 if(data->conn->bits.doh)
1308 /* nothing to wait for during DoH resolve, those handles have their own
1309 sockets */
1310 return GETSOCK_BLANK;
1311#endif
1312 return Curl_resolver_getsock(data, sock: socks);
1313#else
1314 (void)data;
1315 (void)socks;
1316 return GETSOCK_BLANK;
1317#endif
1318}
1319
1320/* Call this function after Curl_connect() has returned async=TRUE and
1321 then a successful name resolve has been received.
1322
1323 Note: this function disconnects and frees the conn data in case of
1324 resolve failure */
1325CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done)
1326{
1327 CURLcode result;
1328 struct connectdata *conn = data->conn;
1329
1330#ifdef USE_CURL_ASYNC
1331 if(data->state.async.dns) {
1332 conn->dns_entry = data->state.async.dns;
1333 data->state.async.dns = NULL;
1334 }
1335#endif
1336
1337 result = Curl_setup_conn(data, protocol_done);
1338
1339 if(result) {
1340 Curl_detach_connection(data);
1341 Curl_conncache_remove_conn(data, conn, TRUE);
1342 Curl_disconnect(data, conn, TRUE);
1343 }
1344 return result;
1345}
1346
1347/*
1348 * Curl_resolver_error() calls failf() with the appropriate message after a
1349 * resolve error
1350 */
1351
1352#ifdef USE_CURL_ASYNC
1353CURLcode Curl_resolver_error(struct Curl_easy *data)
1354{
1355 const char *host_or_proxy;
1356 CURLcode result;
1357
1358#ifndef CURL_DISABLE_PROXY
1359 struct connectdata *conn = data->conn;
1360 if(conn->bits.httpproxy) {
1361 host_or_proxy = "proxy";
1362 result = CURLE_COULDNT_RESOLVE_PROXY;
1363 }
1364 else
1365#endif
1366 {
1367 host_or_proxy = "host";
1368 result = CURLE_COULDNT_RESOLVE_HOST;
1369 }
1370
1371 failf(data, fmt: "Could not resolve %s: %s", host_or_proxy,
1372 data->state.async.hostname);
1373
1374 return result;
1375}
1376#endif /* USE_CURL_ASYNC */
1377