1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, 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.haxx.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 ***************************************************************************/
22
23 #include "curl_setup.h"
24
25 #ifdef HAVE_NETINET_IN_H
26 #include <netinet/in.h>
27 #endif
28 #ifdef HAVE_NETINET_IN6_H
29 #include <netinet/in6.h>
30 #endif
31 #ifdef HAVE_NETDB_H
32 #include <netdb.h>
33 #endif
34 #ifdef HAVE_ARPA_INET_H
35 #include <arpa/inet.h>
36 #endif
37 #ifdef __VMS
38 #include <in.h>
39 #include <inet.h>
40 #endif
41
42 #ifdef HAVE_SETJMP_H
43 #include <setjmp.h>
44 #endif
45 #ifdef HAVE_SIGNAL_H
46 #include <signal.h>
47 #endif
48
49 #ifdef HAVE_PROCESS_H
50 #include <process.h>
51 #endif
52
53 #include "urldata.h"
54 #include "sendf.h"
55 #include "hostip.h"
56 #include "hash.h"
57 #include "rand.h"
58 #include "share.h"
59 #include "strerror.h"
60 #include "url.h"
61 #include "inet_ntop.h"
62 #include "multiif.h"
63 #include "doh.h"
64 #include "warnless.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) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
72 /* alarm-based timeouts can only be used with all the dependencies satisfied */
73 #define USE_ALARM_TIMEOUT
74 #endif
75
76 #define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
77
78 /*
79 * hostip.c explained
80 * ==================
81 *
82 * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
83 * source file are these:
84 *
85 * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
86 * that. The host may not be able to resolve IPv6, but we don't really have to
87 * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
88 * defined.
89 *
90 * CURLRES_ARES - is defined if libcurl is built to use c-ares for
91 * asynchronous name resolves. This can be Windows or *nix.
92 *
93 * CURLRES_THREADED - is defined if libcurl is built to run under (native)
94 * Windows, and then the name resolve will be done in a new thread, and the
95 * supported API will be the same as for ares-builds.
96 *
97 * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
98 * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
99 * defined.
100 *
101 * The host*.c sources files are split up like this:
102 *
103 * hostip.c - method-independent resolver functions and utility functions
104 * hostasyn.c - functions for asynchronous name resolves
105 * hostsyn.c - functions for synchronous name resolves
106 * hostip4.c - IPv4 specific functions
107 * hostip6.c - IPv6 specific functions
108 *
109 * The two asynchronous name resolver backends are implemented in:
110 * asyn-ares.c - functions for ares-using name resolves
111 * asyn-thread.c - functions for threaded name resolves
112
113 * The hostip.h is the united header file for all this. It defines the
114 * CURLRES_* defines based on the config*.h and curl_setup.h defines.
115 */
116
117 /* These two symbols are for the global DNS cache */
118 static struct curl_hash hostname_cache;
119 static int host_cache_initialized;
120
121 static void freednsentry(void *freethis);
122
123 /*
124 * Curl_global_host_cache_init() initializes and sets up a global DNS cache.
125 * Global DNS cache is general badness. Do not use. This will be removed in
126 * a future version. Use the share interface instead!
127 *
128 * Returns a struct curl_hash pointer on success, NULL on failure.
129 */
Curl_global_host_cache_init(void)130 struct curl_hash *Curl_global_host_cache_init(void)
131 {
132 int rc = 0;
133 if(!host_cache_initialized) {
134 rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
135 Curl_str_key_compare, freednsentry);
136 if(!rc)
137 host_cache_initialized = 1;
138 }
139 return rc?NULL:&hostname_cache;
140 }
141
142 /*
143 * Destroy and cleanup the global DNS cache
144 */
Curl_global_host_cache_dtor(void)145 void Curl_global_host_cache_dtor(void)
146 {
147 if(host_cache_initialized) {
148 Curl_hash_destroy(&hostname_cache);
149 host_cache_initialized = 0;
150 }
151 }
152
153 /*
154 * Return # of addresses in a Curl_addrinfo struct
155 */
Curl_num_addresses(const Curl_addrinfo * addr)156 int Curl_num_addresses(const Curl_addrinfo *addr)
157 {
158 int i = 0;
159 while(addr) {
160 addr = addr->ai_next;
161 i++;
162 }
163 return i;
164 }
165
166 /*
167 * Curl_printable_address() returns a printable version of the 1st address
168 * given in the 'ai' argument. The result will be stored in the buf that is
169 * bufsize bytes big.
170 *
171 * If the conversion fails, it returns NULL.
172 */
173 const char *
Curl_printable_address(const Curl_addrinfo * ai,char * buf,size_t bufsize)174 Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize)
175 {
176 const struct sockaddr_in *sa4;
177 const struct in_addr *ipaddr4;
178 #ifdef ENABLE_IPV6
179 const struct sockaddr_in6 *sa6;
180 const struct in6_addr *ipaddr6;
181 #endif
182
183 switch(ai->ai_family) {
184 case AF_INET:
185 sa4 = (const void *)ai->ai_addr;
186 ipaddr4 = &sa4->sin_addr;
187 return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf,
188 bufsize);
189 #ifdef ENABLE_IPV6
190 case AF_INET6:
191 sa6 = (const void *)ai->ai_addr;
192 ipaddr6 = &sa6->sin6_addr;
193 return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf,
194 bufsize);
195 #endif
196 default:
197 break;
198 }
199 return NULL;
200 }
201
202 /*
203 * Create a hostcache id string for the provided host + port, to be used by
204 * the DNS caching. Without alloc.
205 */
206 static void
create_hostcache_id(const char * name,int port,char * ptr,size_t buflen)207 create_hostcache_id(const char *name, int port, char *ptr, size_t buflen)
208 {
209 size_t len = strlen(name);
210 if(len > (buflen - 7))
211 len = buflen - 7;
212 /* store and lower case the name */
213 while(len--)
214 *ptr++ = (char)TOLOWER(*name++);
215 msnprintf(ptr, 7, ":%u", port);
216 }
217
218 struct hostcache_prune_data {
219 long cache_timeout;
220 time_t now;
221 };
222
223 /*
224 * This function is set as a callback to be called for every entry in the DNS
225 * cache when we want to prune old unused entries.
226 *
227 * Returning non-zero means remove the entry, return 0 to keep it in the
228 * cache.
229 */
230 static int
hostcache_timestamp_remove(void * datap,void * hc)231 hostcache_timestamp_remove(void *datap, void *hc)
232 {
233 struct hostcache_prune_data *data =
234 (struct hostcache_prune_data *) datap;
235 struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
236
237 return (0 != c->timestamp)
238 && (data->now - c->timestamp >= data->cache_timeout);
239 }
240
241 /*
242 * Prune the DNS cache. This assumes that a lock has already been taken.
243 */
244 static void
hostcache_prune(struct curl_hash * hostcache,long cache_timeout,time_t now)245 hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
246 {
247 struct hostcache_prune_data user;
248
249 user.cache_timeout = cache_timeout;
250 user.now = now;
251
252 Curl_hash_clean_with_criterium(hostcache,
253 (void *) &user,
254 hostcache_timestamp_remove);
255 }
256
257 /*
258 * Library-wide function for pruning the DNS cache. This function takes and
259 * returns the appropriate locks.
260 */
Curl_hostcache_prune(struct Curl_easy * data)261 void Curl_hostcache_prune(struct Curl_easy *data)
262 {
263 time_t now;
264
265 if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
266 /* cache forever means never prune, and NULL hostcache means
267 we can't do it */
268 return;
269
270 if(data->share)
271 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
272
273 time(&now);
274
275 /* Remove outdated and unused entries from the hostcache */
276 hostcache_prune(data->dns.hostcache,
277 data->set.dns_cache_timeout,
278 now);
279
280 if(data->share)
281 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
282 }
283
284 #ifdef HAVE_SIGSETJMP
285 /* Beware this is a global and unique instance. This is used to store the
286 return address that we can jump back to from inside a signal handler. This
287 is not thread-safe stuff. */
288 sigjmp_buf curl_jmpenv;
289 #endif
290
291 /* lookup address, returns entry if found and not stale */
292 static struct Curl_dns_entry *
fetch_addr(struct connectdata * conn,const char * hostname,int port)293 fetch_addr(struct connectdata *conn,
294 const char *hostname,
295 int port)
296 {
297 struct Curl_dns_entry *dns = NULL;
298 size_t entry_len;
299 struct Curl_easy *data = conn->data;
300 char entry_id[MAX_HOSTCACHE_LEN];
301
302 /* Create an entry id, based upon the hostname and port */
303 create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
304 entry_len = strlen(entry_id);
305
306 /* See if its already in our dns cache */
307 dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
308
309 /* No entry found in cache, check if we might have a wildcard entry */
310 if(!dns && data->change.wildcard_resolve) {
311 create_hostcache_id("*", port, entry_id, sizeof(entry_id));
312 entry_len = strlen(entry_id);
313
314 /* See if it's already in our dns cache */
315 dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
316 }
317
318 if(dns && (data->set.dns_cache_timeout != -1)) {
319 /* See whether the returned entry is stale. Done before we release lock */
320 struct hostcache_prune_data user;
321
322 time(&user.now);
323 user.cache_timeout = data->set.dns_cache_timeout;
324
325 if(hostcache_timestamp_remove(&user, dns)) {
326 infof(data, "Hostname in DNS cache was stale, zapped\n");
327 dns = NULL; /* the memory deallocation is being handled by the hash */
328 Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
329 }
330 }
331
332 return dns;
333 }
334
335 /*
336 * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
337 *
338 * Curl_resolv() checks initially and multi_runsingle() checks each time
339 * it discovers the handle in the state WAITRESOLVE whether the hostname
340 * has already been resolved and the address has already been stored in
341 * the DNS cache. This short circuits waiting for a lot of pending
342 * lookups for the same hostname requested by different handles.
343 *
344 * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
345 *
346 * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
347 * use, or we'll leak memory!
348 */
349 struct Curl_dns_entry *
Curl_fetch_addr(struct connectdata * conn,const char * hostname,int port)350 Curl_fetch_addr(struct connectdata *conn,
351 const char *hostname,
352 int port)
353 {
354 struct Curl_easy *data = conn->data;
355 struct Curl_dns_entry *dns = NULL;
356
357 if(data->share)
358 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
359
360 dns = fetch_addr(conn, hostname, port);
361
362 if(dns)
363 dns->inuse++; /* we use it! */
364
365 if(data->share)
366 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
367
368 return dns;
369 }
370
371 UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
372 Curl_addrinfo **addr);
373
374 /*
375 * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
376 * struct by re-linking its linked list.
377 *
378 * The addr argument should be the address of a pointer to the head node of a
379 * `Curl_addrinfo` list and it will be modified to point to the new head after
380 * shuffling.
381 *
382 * Not declared static only to make it easy to use in a unit test!
383 *
384 * @unittest: 1608
385 */
Curl_shuffle_addr(struct Curl_easy * data,Curl_addrinfo ** addr)386 UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
387 Curl_addrinfo **addr)
388 {
389 CURLcode result = CURLE_OK;
390 const int num_addrs = Curl_num_addresses(*addr);
391
392 if(num_addrs > 1) {
393 Curl_addrinfo **nodes;
394 infof(data, "Shuffling %i addresses", num_addrs);
395
396 nodes = malloc(num_addrs*sizeof(*nodes));
397 if(nodes) {
398 int i;
399 unsigned int *rnd;
400 const size_t rnd_size = num_addrs * sizeof(*rnd);
401
402 /* build a plain array of Curl_addrinfo pointers */
403 nodes[0] = *addr;
404 for(i = 1; i < num_addrs; i++) {
405 nodes[i] = nodes[i-1]->ai_next;
406 }
407
408 rnd = malloc(rnd_size);
409 if(rnd) {
410 /* Fisher-Yates shuffle */
411 if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
412 Curl_addrinfo *swap_tmp;
413 for(i = num_addrs - 1; i > 0; i--) {
414 swap_tmp = nodes[rnd[i] % (i + 1)];
415 nodes[rnd[i] % (i + 1)] = nodes[i];
416 nodes[i] = swap_tmp;
417 }
418
419 /* relink list in the new order */
420 for(i = 1; i < num_addrs; i++) {
421 nodes[i-1]->ai_next = nodes[i];
422 }
423
424 nodes[num_addrs-1]->ai_next = NULL;
425 *addr = nodes[0];
426 }
427 free(rnd);
428 }
429 else
430 result = CURLE_OUT_OF_MEMORY;
431 free(nodes);
432 }
433 else
434 result = CURLE_OUT_OF_MEMORY;
435 }
436 return result;
437 }
438
439 /*
440 * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
441 *
442 * When calling Curl_resolv() has resulted in a response with a returned
443 * address, we call this function to store the information in the dns
444 * cache etc
445 *
446 * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
447 */
448 struct Curl_dns_entry *
Curl_cache_addr(struct Curl_easy * data,Curl_addrinfo * addr,const char * hostname,int port)449 Curl_cache_addr(struct Curl_easy *data,
450 Curl_addrinfo *addr,
451 const char *hostname,
452 int port)
453 {
454 char entry_id[MAX_HOSTCACHE_LEN];
455 size_t entry_len;
456 struct Curl_dns_entry *dns;
457 struct Curl_dns_entry *dns2;
458
459 /* shuffle addresses if requested */
460 if(data->set.dns_shuffle_addresses) {
461 CURLcode result = Curl_shuffle_addr(data, &addr);
462 if(result)
463 return NULL;
464 }
465
466 /* Create a new cache entry */
467 dns = calloc(1, sizeof(struct Curl_dns_entry));
468 if(!dns) {
469 return NULL;
470 }
471
472 /* Create an entry id, based upon the hostname and port */
473 create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
474 entry_len = strlen(entry_id);
475
476 dns->inuse = 1; /* the cache has the first reference */
477 dns->addr = addr; /* this is the address(es) */
478 time(&dns->timestamp);
479 if(dns->timestamp == 0)
480 dns->timestamp = 1; /* zero indicates CURLOPT_RESOLVE entry */
481
482 /* Store the resolved data in our DNS cache. */
483 dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1,
484 (void *)dns);
485 if(!dns2) {
486 free(dns);
487 return NULL;
488 }
489
490 dns = dns2;
491 dns->inuse++; /* mark entry as in-use */
492 return dns;
493 }
494
495 /*
496 * Curl_resolv() is the main name resolve function within libcurl. It resolves
497 * a name and returns a pointer to the entry in the 'entry' argument (if one
498 * is provided). This function might return immediately if we're using asynch
499 * resolves. See the return codes.
500 *
501 * The cache entry we return will get its 'inuse' counter increased when this
502 * function is used. You MUST call Curl_resolv_unlock() later (when you're
503 * done using this struct) to decrease the counter again.
504 *
505 * In debug mode, we specifically test for an interface name "LocalHost"
506 * and resolve "localhost" instead as a means to permit test cases
507 * to connect to a local test server with any host name.
508 *
509 * Return codes:
510 *
511 * CURLRESOLV_ERROR (-1) = error, no pointer
512 * CURLRESOLV_RESOLVED (0) = OK, pointer provided
513 * CURLRESOLV_PENDING (1) = waiting for response, no pointer
514 */
515
Curl_resolv(struct connectdata * conn,const char * hostname,int port,struct Curl_dns_entry ** entry)516 int Curl_resolv(struct connectdata *conn,
517 const char *hostname,
518 int port,
519 struct Curl_dns_entry **entry)
520 {
521 struct Curl_dns_entry *dns = NULL;
522 struct Curl_easy *data = conn->data;
523 CURLcode result;
524 int rc = CURLRESOLV_ERROR; /* default to failure */
525
526 *entry = NULL;
527
528 if(data->share)
529 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
530
531 dns = fetch_addr(conn, hostname, port);
532
533 if(dns) {
534 infof(data, "Hostname %s was found in DNS cache\n", hostname);
535 dns->inuse++; /* we use it! */
536 rc = CURLRESOLV_RESOLVED;
537 }
538
539 if(data->share)
540 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
541
542 if(!dns) {
543 /* The entry was not in the cache. Resolve it to IP address */
544
545 Curl_addrinfo *addr;
546 int respwait = 0;
547
548 /* Check what IP specifics the app has requested and if we can provide it.
549 * If not, bail out. */
550 if(!Curl_ipvalid(conn))
551 return CURLRESOLV_ERROR;
552
553 /* notify the resolver start callback */
554 if(data->set.resolver_start) {
555 int st;
556 Curl_set_in_callback(data, true);
557 st = data->set.resolver_start(data->state.resolver, NULL,
558 data->set.resolver_start_client);
559 Curl_set_in_callback(data, false);
560 if(st)
561 return CURLRESOLV_ERROR;
562 }
563
564 if(data->set.doh) {
565 addr = Curl_doh(conn, hostname, port, &respwait);
566 }
567 else {
568 /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
569 non-zero value indicating that we need to wait for the response to the
570 resolve call */
571 addr = Curl_getaddrinfo(conn,
572 #ifdef DEBUGBUILD
573 (data->set.str[STRING_DEVICE]
574 && !strcmp(data->set.str[STRING_DEVICE],
575 "LocalHost"))?"localhost":
576 #endif
577 hostname, port, &respwait);
578 }
579 if(!addr) {
580 if(respwait) {
581 /* the response to our resolve call will come asynchronously at
582 a later time, good or bad */
583 /* First, check that we haven't received the info by now */
584 result = Curl_resolv_check(conn, &dns);
585 if(result) /* error detected */
586 return CURLRESOLV_ERROR;
587 if(dns)
588 rc = CURLRESOLV_RESOLVED; /* pointer provided */
589 else
590 rc = CURLRESOLV_PENDING; /* no info yet */
591 }
592 }
593 else {
594 if(data->share)
595 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
596
597 /* we got a response, store it in the cache */
598 dns = Curl_cache_addr(data, addr, hostname, port);
599
600 if(data->share)
601 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
602
603 if(!dns)
604 /* returned failure, bail out nicely */
605 Curl_freeaddrinfo(addr);
606 else
607 rc = CURLRESOLV_RESOLVED;
608 }
609 }
610
611 *entry = dns;
612
613 return rc;
614 }
615
616 #ifdef USE_ALARM_TIMEOUT
617 /*
618 * This signal handler jumps back into the main libcurl code and continues
619 * execution. This effectively causes the remainder of the application to run
620 * within a signal handler which is nonportable and could lead to problems.
621 */
622 static
alarmfunc(int sig)623 RETSIGTYPE alarmfunc(int sig)
624 {
625 /* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */
626 (void)sig;
627 siglongjmp(curl_jmpenv, 1);
628 }
629 #endif /* USE_ALARM_TIMEOUT */
630
631 /*
632 * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
633 * timeout. This function might return immediately if we're using asynch
634 * resolves. See the return codes.
635 *
636 * The cache entry we return will get its 'inuse' counter increased when this
637 * function is used. You MUST call Curl_resolv_unlock() later (when you're
638 * done using this struct) to decrease the counter again.
639 *
640 * If built with a synchronous resolver and use of signals is not
641 * disabled by the application, then a nonzero timeout will cause a
642 * timeout after the specified number of milliseconds. Otherwise, timeout
643 * is ignored.
644 *
645 * Return codes:
646 *
647 * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
648 * CURLRESOLV_ERROR (-1) = error, no pointer
649 * CURLRESOLV_RESOLVED (0) = OK, pointer provided
650 * CURLRESOLV_PENDING (1) = waiting for response, no pointer
651 */
652
Curl_resolv_timeout(struct connectdata * conn,const char * hostname,int port,struct Curl_dns_entry ** entry,time_t timeoutms)653 int Curl_resolv_timeout(struct connectdata *conn,
654 const char *hostname,
655 int port,
656 struct Curl_dns_entry **entry,
657 time_t timeoutms)
658 {
659 #ifdef USE_ALARM_TIMEOUT
660 #ifdef HAVE_SIGACTION
661 struct sigaction keep_sigact; /* store the old struct here */
662 volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
663 struct sigaction sigact;
664 #else
665 #ifdef HAVE_SIGNAL
666 void (*keep_sigact)(int); /* store the old handler here */
667 #endif /* HAVE_SIGNAL */
668 #endif /* HAVE_SIGACTION */
669 volatile long timeout;
670 volatile unsigned int prev_alarm = 0;
671 struct Curl_easy *data = conn->data;
672 #endif /* USE_ALARM_TIMEOUT */
673 int rc;
674
675 *entry = NULL;
676
677 if(timeoutms < 0)
678 /* got an already expired timeout */
679 return CURLRESOLV_TIMEDOUT;
680
681 #ifdef USE_ALARM_TIMEOUT
682 if(data->set.no_signal)
683 /* Ignore the timeout when signals are disabled */
684 timeout = 0;
685 else
686 timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
687
688 if(!timeout)
689 /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
690 return Curl_resolv(conn, hostname, port, entry);
691
692 if(timeout < 1000) {
693 /* The alarm() function only provides integer second resolution, so if
694 we want to wait less than one second we must bail out already now. */
695 failf(data,
696 "remaining timeout of %ld too small to resolve via SIGALRM method",
697 timeout);
698 return CURLRESOLV_TIMEDOUT;
699 }
700 /* This allows us to time-out from the name resolver, as the timeout
701 will generate a signal and we will siglongjmp() from that here.
702 This technique has problems (see alarmfunc).
703 This should be the last thing we do before calling Curl_resolv(),
704 as otherwise we'd have to worry about variables that get modified
705 before we invoke Curl_resolv() (and thus use "volatile"). */
706 if(sigsetjmp(curl_jmpenv, 1)) {
707 /* this is coming from a siglongjmp() after an alarm signal */
708 failf(data, "name lookup timed out");
709 rc = CURLRESOLV_ERROR;
710 goto clean_up;
711 }
712 else {
713 /*************************************************************
714 * Set signal handler to catch SIGALRM
715 * Store the old value to be able to set it back later!
716 *************************************************************/
717 #ifdef HAVE_SIGACTION
718 sigaction(SIGALRM, NULL, &sigact);
719 keep_sigact = sigact;
720 keep_copysig = TRUE; /* yes, we have a copy */
721 sigact.sa_handler = alarmfunc;
722 #ifdef SA_RESTART
723 /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
724 sigact.sa_flags &= ~SA_RESTART;
725 #endif
726 /* now set the new struct */
727 sigaction(SIGALRM, &sigact, NULL);
728 #else /* HAVE_SIGACTION */
729 /* no sigaction(), revert to the much lamer signal() */
730 #ifdef HAVE_SIGNAL
731 keep_sigact = signal(SIGALRM, alarmfunc);
732 #endif
733 #endif /* HAVE_SIGACTION */
734
735 /* alarm() makes a signal get sent when the timeout fires off, and that
736 will abort system calls */
737 prev_alarm = alarm(curlx_sltoui(timeout/1000L));
738 }
739
740 #else
741 #ifndef CURLRES_ASYNCH
742 if(timeoutms)
743 infof(conn->data, "timeout on name lookup is not supported\n");
744 #else
745 (void)timeoutms; /* timeoutms not used with an async resolver */
746 #endif
747 #endif /* USE_ALARM_TIMEOUT */
748
749 /* Perform the actual name resolution. This might be interrupted by an
750 * alarm if it takes too long.
751 */
752 rc = Curl_resolv(conn, hostname, port, entry);
753
754 #ifdef USE_ALARM_TIMEOUT
755 clean_up:
756
757 if(!prev_alarm)
758 /* deactivate a possibly active alarm before uninstalling the handler */
759 alarm(0);
760
761 #ifdef HAVE_SIGACTION
762 if(keep_copysig) {
763 /* we got a struct as it looked before, now put that one back nice
764 and clean */
765 sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
766 }
767 #else
768 #ifdef HAVE_SIGNAL
769 /* restore the previous SIGALRM handler */
770 signal(SIGALRM, keep_sigact);
771 #endif
772 #endif /* HAVE_SIGACTION */
773
774 /* switch back the alarm() to either zero or to what it was before minus
775 the time we spent until now! */
776 if(prev_alarm) {
777 /* there was an alarm() set before us, now put it back */
778 timediff_t elapsed_secs = Curl_timediff(Curl_now(),
779 conn->created) / 1000;
780
781 /* the alarm period is counted in even number of seconds */
782 unsigned long alarm_set = prev_alarm - elapsed_secs;
783
784 if(!alarm_set ||
785 ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
786 /* if the alarm time-left reached zero or turned "negative" (counted
787 with unsigned values), we should fire off a SIGALRM here, but we
788 won't, and zero would be to switch it off so we never set it to
789 less than 1! */
790 alarm(1);
791 rc = CURLRESOLV_TIMEDOUT;
792 failf(data, "Previous alarm fired off!");
793 }
794 else
795 alarm((unsigned int)alarm_set);
796 }
797 #endif /* USE_ALARM_TIMEOUT */
798
799 return rc;
800 }
801
802 /*
803 * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
804 * made, the struct may be destroyed due to pruning. It is important that only
805 * one unlock is made for each Curl_resolv() call.
806 *
807 * May be called with 'data' == NULL for global cache.
808 */
Curl_resolv_unlock(struct Curl_easy * data,struct Curl_dns_entry * dns)809 void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns)
810 {
811 if(data && data->share)
812 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
813
814 freednsentry(dns);
815
816 if(data && data->share)
817 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
818 }
819
820 /*
821 * File-internal: release cache dns entry reference, free if inuse drops to 0
822 */
freednsentry(void * freethis)823 static void freednsentry(void *freethis)
824 {
825 struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
826 DEBUGASSERT(dns && (dns->inuse>0));
827
828 dns->inuse--;
829 if(dns->inuse == 0) {
830 Curl_freeaddrinfo(dns->addr);
831 free(dns);
832 }
833 }
834
835 /*
836 * Curl_mk_dnscache() inits a new DNS cache and returns success/failure.
837 */
Curl_mk_dnscache(struct curl_hash * hash)838 int Curl_mk_dnscache(struct curl_hash *hash)
839 {
840 return Curl_hash_init(hash, 7, Curl_hash_str, Curl_str_key_compare,
841 freednsentry);
842 }
843
844 /*
845 * Curl_hostcache_clean()
846 *
847 * This _can_ be called with 'data' == NULL but then of course no locking
848 * can be done!
849 */
850
Curl_hostcache_clean(struct Curl_easy * data,struct curl_hash * hash)851 void Curl_hostcache_clean(struct Curl_easy *data,
852 struct curl_hash *hash)
853 {
854 if(data && data->share)
855 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
856
857 Curl_hash_clean(hash);
858
859 if(data && data->share)
860 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
861 }
862
863
Curl_loadhostpairs(struct Curl_easy * data)864 CURLcode Curl_loadhostpairs(struct Curl_easy *data)
865 {
866 struct curl_slist *hostp;
867 char hostname[256];
868 int port = 0;
869
870 /* Default is no wildcard found */
871 data->change.wildcard_resolve = false;
872
873 for(hostp = data->change.resolve; hostp; hostp = hostp->next) {
874 char entry_id[MAX_HOSTCACHE_LEN];
875 if(!hostp->data)
876 continue;
877 if(hostp->data[0] == '-') {
878 size_t entry_len;
879
880 if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) {
881 infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'!\n",
882 hostp->data);
883 continue;
884 }
885
886 /* Create an entry id, based upon the hostname and port */
887 create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
888 entry_len = strlen(entry_id);
889
890 if(data->share)
891 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
892
893 /* delete entry, ignore if it didn't exist */
894 Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
895
896 if(data->share)
897 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
898 }
899 else {
900 struct Curl_dns_entry *dns;
901 Curl_addrinfo *head = NULL, *tail = NULL;
902 size_t entry_len;
903 char address[64];
904 #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
905 char *addresses = NULL;
906 #endif
907 char *addr_begin;
908 char *addr_end;
909 char *port_ptr;
910 char *end_ptr;
911 char *host_end;
912 unsigned long tmp_port;
913 bool error = true;
914
915 host_end = strchr(hostp->data, ':');
916 if(!host_end ||
917 ((host_end - hostp->data) >= (ptrdiff_t)sizeof(hostname)))
918 goto err;
919
920 memcpy(hostname, hostp->data, host_end - hostp->data);
921 hostname[host_end - hostp->data] = '\0';
922
923 port_ptr = host_end + 1;
924 tmp_port = strtoul(port_ptr, &end_ptr, 10);
925 if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':')
926 goto err;
927
928 port = (int)tmp_port;
929 #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
930 addresses = end_ptr + 1;
931 #endif
932
933 while(*end_ptr) {
934 size_t alen;
935 Curl_addrinfo *ai;
936
937 addr_begin = end_ptr + 1;
938 addr_end = strchr(addr_begin, ',');
939 if(!addr_end)
940 addr_end = addr_begin + strlen(addr_begin);
941 end_ptr = addr_end;
942
943 /* allow IP(v6) address within [brackets] */
944 if(*addr_begin == '[') {
945 if(addr_end == addr_begin || *(addr_end - 1) != ']')
946 goto err;
947 ++addr_begin;
948 --addr_end;
949 }
950
951 alen = addr_end - addr_begin;
952 if(!alen)
953 continue;
954
955 if(alen >= sizeof(address))
956 goto err;
957
958 memcpy(address, addr_begin, alen);
959 address[alen] = '\0';
960
961 #ifndef ENABLE_IPV6
962 if(strchr(address, ':')) {
963 infof(data, "Ignoring resolve address '%s', missing IPv6 support.\n",
964 address);
965 continue;
966 }
967 #endif
968
969 ai = Curl_str2addr(address, port);
970 if(!ai) {
971 infof(data, "Resolve address '%s' found illegal!\n", address);
972 goto err;
973 }
974
975 if(tail) {
976 tail->ai_next = ai;
977 tail = tail->ai_next;
978 }
979 else {
980 head = tail = ai;
981 }
982 }
983
984 if(!head)
985 goto err;
986
987 error = false;
988 err:
989 if(error) {
990 infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
991 hostp->data);
992 Curl_freeaddrinfo(head);
993 continue;
994 }
995
996 /* Create an entry id, based upon the hostname and port */
997 create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
998 entry_len = strlen(entry_id);
999
1000 if(data->share)
1001 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1002
1003 /* See if its already in our dns cache */
1004 dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
1005
1006 if(dns) {
1007 infof(data, "RESOLVE %s:%d is - old addresses discarded!\n",
1008 hostname, port);
1009 /* delete old entry entry, there are two reasons for this
1010 1. old entry may have different addresses.
1011 2. even if entry with correct addresses is already in the cache,
1012 but if it is close to expire, then by the time next http
1013 request is made, it can get expired and pruned because old
1014 entry is not necessarily marked as added by CURLOPT_RESOLVE. */
1015
1016 Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
1017 }
1018
1019 /* put this new host in the cache */
1020 dns = Curl_cache_addr(data, head, hostname, port);
1021 if(dns) {
1022 dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */
1023 /* release the returned reference; the cache itself will keep the
1024 * entry alive: */
1025 dns->inuse--;
1026 }
1027
1028 if(data->share)
1029 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1030
1031 if(!dns) {
1032 Curl_freeaddrinfo(head);
1033 return CURLE_OUT_OF_MEMORY;
1034 }
1035 infof(data, "Added %s:%d:%s to DNS cache\n",
1036 hostname, port, addresses);
1037
1038 /* Wildcard hostname */
1039 if(hostname[0] == '*' && hostname[1] == '\0') {
1040 infof(data, "RESOLVE %s:%d is wildcard, enabling wildcard checks\n",
1041 hostname, port);
1042 data->change.wildcard_resolve = true;
1043 }
1044 }
1045 }
1046 data->change.resolve = NULL; /* dealt with now */
1047
1048 return CURLE_OK;
1049 }
1050
Curl_resolv_check(struct connectdata * conn,struct Curl_dns_entry ** dns)1051 CURLcode Curl_resolv_check(struct connectdata *conn,
1052 struct Curl_dns_entry **dns)
1053 {
1054 if(conn->data->set.doh)
1055 return Curl_doh_is_resolved(conn, dns);
1056 return Curl_resolver_is_resolved(conn, dns);
1057 }
1058
Curl_resolv_getsock(struct connectdata * conn,curl_socket_t * socks,int numsocks)1059 int Curl_resolv_getsock(struct connectdata *conn,
1060 curl_socket_t *socks,
1061 int numsocks)
1062 {
1063 #ifdef CURLRES_ASYNCH
1064 if(conn->data->set.doh)
1065 /* nothing to wait for during DOH resolve, those handles have their own
1066 sockets */
1067 return GETSOCK_BLANK;
1068 return Curl_resolver_getsock(conn, socks, numsocks);
1069 #else
1070 (void)conn;
1071 (void)socks;
1072 (void)numsocks;
1073 return GETSOCK_BLANK;
1074 #endif
1075 }
1076
1077 /* Call this function after Curl_connect() has returned async=TRUE and
1078 then a successful name resolve has been received.
1079
1080 Note: this function disconnects and frees the conn data in case of
1081 resolve failure */
Curl_once_resolved(struct connectdata * conn,bool * protocol_done)1082 CURLcode Curl_once_resolved(struct connectdata *conn,
1083 bool *protocol_done)
1084 {
1085 CURLcode result;
1086
1087 if(conn->async.dns) {
1088 conn->dns_entry = conn->async.dns;
1089 conn->async.dns = NULL;
1090 }
1091
1092 result = Curl_setup_conn(conn, protocol_done);
1093
1094 if(result)
1095 /* We're not allowed to return failure with memory left allocated
1096 in the connectdata struct, free those here */
1097 Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
1098
1099 return result;
1100 }
1101