VirtualBox

source: vbox/trunk/src/libs/curl-8.3.0/lib/hostip.c@ 101259

Last change on this file since 101259 was 101127, checked in by vboxsync, 19 months ago

curl-8.3.0: Applied and adjusted our curl changes to 8.0.1. bugref:10526

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette