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