• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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