f37b4925bb3990363a3c8ec0903797aa31ff79fe
[platform/upstream/curl.git] / lib / hostip.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2013, 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 http://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_NETDB_H
29 #include <netdb.h>
30 #endif
31 #ifdef HAVE_ARPA_INET_H
32 #include <arpa/inet.h>
33 #endif
34 #ifdef __VMS
35 #include <in.h>
36 #include <inet.h>
37 #endif
38
39 #ifdef HAVE_SETJMP_H
40 #include <setjmp.h>
41 #endif
42 #ifdef HAVE_SIGNAL_H
43 #include <signal.h>
44 #endif
45
46 #ifdef HAVE_PROCESS_H
47 #include <process.h>
48 #endif
49
50 #include "urldata.h"
51 #include "sendf.h"
52 #include "hostip.h"
53 #include "hash.h"
54 #include "share.h"
55 #include "strerror.h"
56 #include "url.h"
57 #include "inet_ntop.h"
58 #include "warnless.h"
59
60 #define _MPRINTF_REPLACE /* use our functions only */
61 #include <curl/mprintf.h>
62
63 #include "curl_memory.h"
64 /* The last #include file should be: */
65 #include "memdebug.h"
66
67 #if defined(CURLRES_SYNCH) && \
68     defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
69 /* alarm-based timeouts can only be used with all the dependencies satisfied */
70 #define USE_ALARM_TIMEOUT
71 #endif
72
73 /*
74  * hostip.c explained
75  * ==================
76  *
77  * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
78  * source file are these:
79  *
80  * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
81  * that. The host may not be able to resolve IPv6, but we don't really have to
82  * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
83  * defined.
84  *
85  * CURLRES_ARES - is defined if libcurl is built to use c-ares for
86  * asynchronous name resolves. This can be Windows or *nix.
87  *
88  * CURLRES_THREADED - is defined if libcurl is built to run under (native)
89  * Windows, and then the name resolve will be done in a new thread, and the
90  * supported API will be the same as for ares-builds.
91  *
92  * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
93  * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
94  * defined.
95  *
96  * The host*.c sources files are split up like this:
97  *
98  * hostip.c   - method-independent resolver functions and utility functions
99  * hostasyn.c - functions for asynchronous name resolves
100  * hostsyn.c  - functions for synchronous name resolves
101  * hostip4.c  - ipv4-specific functions
102  * hostip6.c  - ipv6-specific functions
103  *
104  * The two asynchronous name resolver backends are implemented in:
105  * asyn-ares.c   - functions for ares-using name resolves
106  * asyn-thread.c - functions for threaded name resolves
107
108  * The hostip.h is the united header file for all this. It defines the
109  * CURLRES_* defines based on the config*.h and curl_setup.h defines.
110  */
111
112 /* These two symbols are for the global DNS cache */
113 static struct curl_hash hostname_cache;
114 static int host_cache_initialized;
115
116 static void freednsentry(void *freethis);
117
118 /*
119  * Curl_global_host_cache_init() initializes and sets up a global DNS cache.
120  * Global DNS cache is general badness. Do not use. This will be removed in
121  * a future version. Use the share interface instead!
122  *
123  * Returns a struct curl_hash pointer on success, NULL on failure.
124  */
125 struct curl_hash *Curl_global_host_cache_init(void)
126 {
127   int rc = 0;
128   if(!host_cache_initialized) {
129     rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
130                         Curl_str_key_compare, freednsentry);
131     if(!rc)
132       host_cache_initialized = 1;
133   }
134   return rc?NULL:&hostname_cache;
135 }
136
137 /*
138  * Destroy and cleanup the global DNS cache
139  */
140 void Curl_global_host_cache_dtor(void)
141 {
142   if(host_cache_initialized) {
143     /* first make sure that any custom "CURLOPT_RESOLVE" names are
144        cleared off */
145     Curl_hostcache_clean(NULL, &hostname_cache);
146     /* then free the remaining hash completely */
147     Curl_hash_clean(&hostname_cache);
148     host_cache_initialized = 0;
149   }
150 }
151
152 /*
153  * Return # of adresses in a Curl_addrinfo struct
154  */
155 int Curl_num_addresses(const Curl_addrinfo *addr)
156 {
157   int i = 0;
158   while(addr) {
159     addr = addr->ai_next;
160     i++;
161   }
162   return i;
163 }
164
165 /*
166  * Curl_printable_address() returns a printable version of the 1st address
167  * given in the 'ai' argument. The result will be stored in the buf that is
168  * bufsize bytes big.
169  *
170  * If the conversion fails, it returns NULL.
171  */
172 const char *
173 Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize)
174 {
175   const struct sockaddr_in *sa4;
176   const struct in_addr *ipaddr4;
177 #ifdef ENABLE_IPV6
178   const struct sockaddr_in6 *sa6;
179   const struct in6_addr *ipaddr6;
180 #endif
181
182   switch (ai->ai_family) {
183     case AF_INET:
184       sa4 = (const void *)ai->ai_addr;
185       ipaddr4 = &sa4->sin_addr;
186       return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf,
187                             bufsize);
188 #ifdef ENABLE_IPV6
189     case AF_INET6:
190       sa6 = (const void *)ai->ai_addr;
191       ipaddr6 = &sa6->sin6_addr;
192       return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf,
193                             bufsize);
194 #endif
195     default:
196       break;
197   }
198   return NULL;
199 }
200
201 /*
202  * Return a hostcache id string for the provided host + port, to be used by
203  * the DNS caching.
204  */
205 static char *
206 create_hostcache_id(const char *name, int port)
207 {
208   /* create and return the new allocated entry */
209   char *id = aprintf("%s:%d", name, port);
210   char *ptr = id;
211   if(ptr) {
212     /* lower case the name part */
213     while(*ptr && (*ptr != ':')) {
214       *ptr = (char)TOLOWER(*ptr);
215       ptr++;
216     }
217   }
218   return id;
219 }
220
221 struct hostcache_prune_data {
222   long cache_timeout;
223   time_t now;
224 };
225
226 /*
227  * This function is set as a callback to be called for every entry in the DNS
228  * cache when we want to prune old unused entries.
229  *
230  * Returning non-zero means remove the entry, return 0 to keep it in the
231  * cache.
232  */
233 static int
234 hostcache_timestamp_remove(void *datap, void *hc)
235 {
236   struct hostcache_prune_data *data =
237     (struct hostcache_prune_data *) datap;
238   struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
239
240   return (data->now - c->timestamp >= data->cache_timeout);
241 }
242
243 /*
244  * Prune the DNS cache. This assumes that a lock has already been taken.
245  */
246 static void
247 hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
248 {
249   struct hostcache_prune_data user;
250
251   user.cache_timeout = cache_timeout;
252   user.now = now;
253
254   Curl_hash_clean_with_criterium(hostcache,
255                                  (void *) &user,
256                                  hostcache_timestamp_remove);
257 }
258
259 /*
260  * Library-wide function for pruning the DNS cache. This function takes and
261  * returns the appropriate locks.
262  */
263 void Curl_hostcache_prune(struct SessionHandle *data)
264 {
265   time_t now;
266
267   if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
268     /* cache forever means never prune, and NULL hostcache means
269        we can't do it */
270     return;
271
272   if(data->share)
273     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
274
275   time(&now);
276
277   /* Remove outdated and unused entries from the hostcache */
278   hostcache_prune(data->dns.hostcache,
279                   data->set.dns_cache_timeout,
280                   now);
281
282   if(data->share)
283     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
284 }
285
286 /*
287  * Check if the entry should be pruned. Assumes a locked cache.
288  */
289 static int
290 remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
291 {
292   struct hostcache_prune_data user;
293
294   if(!dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
295     /* cache forever means never prune, and NULL hostcache means
296        we can't do it */
297     return 0;
298
299   time(&user.now);
300   user.cache_timeout = data->set.dns_cache_timeout;
301
302   if(!hostcache_timestamp_remove(&user,dns) )
303     return 0;
304
305   Curl_hash_clean_with_criterium(data->dns.hostcache,
306                                  (void *) &user,
307                                  hostcache_timestamp_remove);
308
309   return 1;
310 }
311
312
313 #ifdef HAVE_SIGSETJMP
314 /* Beware this is a global and unique instance. This is used to store the
315    return address that we can jump back to from inside a signal handler. This
316    is not thread-safe stuff. */
317 sigjmp_buf curl_jmpenv;
318 #endif
319
320
321 /*
322  * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
323  *
324  * When calling Curl_resolv() has resulted in a response with a returned
325  * address, we call this function to store the information in the dns
326  * cache etc
327  *
328  * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
329  */
330 struct Curl_dns_entry *
331 Curl_cache_addr(struct SessionHandle *data,
332                 Curl_addrinfo *addr,
333                 const char *hostname,
334                 int port)
335 {
336   char *entry_id;
337   size_t entry_len;
338   struct Curl_dns_entry *dns;
339   struct Curl_dns_entry *dns2;
340
341   /* Create an entry id, based upon the hostname and port */
342   entry_id = create_hostcache_id(hostname, port);
343   /* If we can't create the entry id, fail */
344   if(!entry_id)
345     return NULL;
346   entry_len = strlen(entry_id);
347
348   /* Create a new cache entry */
349   dns = calloc(1, sizeof(struct Curl_dns_entry));
350   if(!dns) {
351     free(entry_id);
352     return NULL;
353   }
354
355   dns->inuse = 0;   /* init to not used */
356   dns->addr = addr; /* this is the address(es) */
357   time(&dns->timestamp);
358   if(dns->timestamp == 0)
359     dns->timestamp = 1;   /* zero indicates that entry isn't in hash table */
360
361   /* Store the resolved data in our DNS cache. */
362   dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
363                        (void *)dns);
364   if(!dns2) {
365     free(dns);
366     free(entry_id);
367     return NULL;
368   }
369
370   dns = dns2;
371   dns->inuse++;         /* mark entry as in-use */
372
373   /* free the allocated entry_id */
374   free(entry_id);
375
376   return dns;
377 }
378
379 /*
380  * Curl_resolv() is the main name resolve function within libcurl. It resolves
381  * a name and returns a pointer to the entry in the 'entry' argument (if one
382  * is provided). This function might return immediately if we're using asynch
383  * resolves. See the return codes.
384  *
385  * The cache entry we return will get its 'inuse' counter increased when this
386  * function is used. You MUST call Curl_resolv_unlock() later (when you're
387  * done using this struct) to decrease the counter again.
388  *
389  * In debug mode, we specifically test for an interface name "LocalHost"
390  * and resolve "localhost" instead as a means to permit test cases
391  * to connect to a local test server with any host name.
392  *
393  * Return codes:
394  *
395  * CURLRESOLV_ERROR   (-1) = error, no pointer
396  * CURLRESOLV_RESOLVED (0) = OK, pointer provided
397  * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
398  */
399
400 int Curl_resolv(struct connectdata *conn,
401                 const char *hostname,
402                 int port,
403                 struct Curl_dns_entry **entry)
404 {
405   char *entry_id = NULL;
406   struct Curl_dns_entry *dns = NULL;
407   size_t entry_len;
408   struct SessionHandle *data = conn->data;
409   CURLcode result;
410   int rc = CURLRESOLV_ERROR; /* default to failure */
411
412   *entry = NULL;
413
414   /* Create an entry id, based upon the hostname and port */
415   entry_id = create_hostcache_id(hostname, port);
416   /* If we can't create the entry id, fail */
417   if(!entry_id)
418     return rc;
419
420   entry_len = strlen(entry_id);
421
422   if(data->share)
423     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
424
425   /* See if its already in our dns cache */
426   dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
427
428   /* free the allocated entry_id again */
429   free(entry_id);
430
431   /* See whether the returned entry is stale. Done before we release lock */
432   if(remove_entry_if_stale(data, dns))
433     dns = NULL; /* the memory deallocation is being handled by the hash */
434
435   if(dns) {
436     dns->inuse++; /* we use it! */
437     rc = CURLRESOLV_RESOLVED;
438   }
439
440   if(data->share)
441     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
442
443   if(!dns) {
444     /* The entry was not in the cache. Resolve it to IP address */
445
446     Curl_addrinfo *addr;
447     int respwait;
448
449     /* Check what IP specifics the app has requested and if we can provide it.
450      * If not, bail out. */
451     if(!Curl_ipvalid(conn))
452       return CURLRESOLV_ERROR;
453
454     /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
455        non-zero value indicating that we need to wait for the response to the
456        resolve call */
457     addr = Curl_getaddrinfo(conn,
458 #ifdef DEBUGBUILD
459                             (data->set.str[STRING_DEVICE]
460                              && !strcmp(data->set.str[STRING_DEVICE],
461                                         "LocalHost"))?"localhost":
462 #endif
463                             hostname, port, &respwait);
464
465     if(!addr) {
466       if(respwait) {
467         /* the response to our resolve call will come asynchronously at
468            a later time, good or bad */
469         /* First, check that we haven't received the info by now */
470         result = Curl_resolver_is_resolved(conn, &dns);
471         if(result) /* error detected */
472           return CURLRESOLV_ERROR;
473         if(dns)
474           rc = CURLRESOLV_RESOLVED; /* pointer provided */
475         else
476           rc = CURLRESOLV_PENDING; /* no info yet */
477       }
478     }
479     else {
480       if(data->share)
481         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
482
483       /* we got a response, store it in the cache */
484       dns = Curl_cache_addr(data, addr, hostname, port);
485
486       if(data->share)
487         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
488
489       if(!dns)
490         /* returned failure, bail out nicely */
491         Curl_freeaddrinfo(addr);
492       else
493         rc = CURLRESOLV_RESOLVED;
494     }
495   }
496
497   *entry = dns;
498
499   return rc;
500 }
501
502 #ifdef USE_ALARM_TIMEOUT
503 /*
504  * This signal handler jumps back into the main libcurl code and continues
505  * execution.  This effectively causes the remainder of the application to run
506  * within a signal handler which is nonportable and could lead to problems.
507  */
508 static
509 RETSIGTYPE alarmfunc(int sig)
510 {
511   /* this is for "-ansi -Wall -pedantic" to stop complaining!   (rabe) */
512   (void)sig;
513   siglongjmp(curl_jmpenv, 1);
514   return;
515 }
516 #endif /* USE_ALARM_TIMEOUT */
517
518 /*
519  * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
520  * timeout.  This function might return immediately if we're using asynch
521  * resolves. See the return codes.
522  *
523  * The cache entry we return will get its 'inuse' counter increased when this
524  * function is used. You MUST call Curl_resolv_unlock() later (when you're
525  * done using this struct) to decrease the counter again.
526  *
527  * If built with a synchronous resolver and use of signals is not
528  * disabled by the application, then a nonzero timeout will cause a
529  * timeout after the specified number of milliseconds. Otherwise, timeout
530  * is ignored.
531  *
532  * Return codes:
533  *
534  * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
535  * CURLRESOLV_ERROR   (-1) = error, no pointer
536  * CURLRESOLV_RESOLVED (0) = OK, pointer provided
537  * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
538  */
539
540 int Curl_resolv_timeout(struct connectdata *conn,
541                         const char *hostname,
542                         int port,
543                         struct Curl_dns_entry **entry,
544                         long timeoutms)
545 {
546 #ifdef USE_ALARM_TIMEOUT
547 #ifdef HAVE_SIGACTION
548   struct sigaction keep_sigact;   /* store the old struct here */
549   volatile bool keep_copysig = FALSE; /* wether old sigact has been saved */
550   struct sigaction sigact;
551 #else
552 #ifdef HAVE_SIGNAL
553   void (*keep_sigact)(int);       /* store the old handler here */
554 #endif /* HAVE_SIGNAL */
555 #endif /* HAVE_SIGACTION */
556   volatile long timeout;
557   volatile unsigned int prev_alarm = 0;
558   struct SessionHandle *data = conn->data;
559 #endif /* USE_ALARM_TIMEOUT */
560   int rc;
561
562   *entry = NULL;
563
564   if(timeoutms < 0)
565     /* got an already expired timeout */
566     return CURLRESOLV_TIMEDOUT;
567
568 #ifdef USE_ALARM_TIMEOUT
569   if(data->set.no_signal)
570     /* Ignore the timeout when signals are disabled */
571     timeout = 0;
572   else
573     timeout = timeoutms;
574
575   if(!timeout)
576     /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
577     return Curl_resolv(conn, hostname, port, entry);
578
579   if(timeout < 1000)
580     /* The alarm() function only provides integer second resolution, so if
581        we want to wait less than one second we must bail out already now. */
582     return CURLRESOLV_TIMEDOUT;
583
584   /*************************************************************
585    * Set signal handler to catch SIGALRM
586    * Store the old value to be able to set it back later!
587    *************************************************************/
588 #ifdef HAVE_SIGACTION
589   sigaction(SIGALRM, NULL, &sigact);
590   keep_sigact = sigact;
591   keep_copysig = TRUE; /* yes, we have a copy */
592   sigact.sa_handler = alarmfunc;
593 #ifdef SA_RESTART
594   /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
595   sigact.sa_flags &= ~SA_RESTART;
596 #endif
597   /* now set the new struct */
598   sigaction(SIGALRM, &sigact, NULL);
599 #else /* HAVE_SIGACTION */
600   /* no sigaction(), revert to the much lamer signal() */
601 #ifdef HAVE_SIGNAL
602   keep_sigact = signal(SIGALRM, alarmfunc);
603 #endif
604 #endif /* HAVE_SIGACTION */
605
606   /* alarm() makes a signal get sent when the timeout fires off, and that
607      will abort system calls */
608   prev_alarm = alarm(curlx_sltoui(timeout/1000L));
609
610   /* This allows us to time-out from the name resolver, as the timeout
611      will generate a signal and we will siglongjmp() from that here.
612      This technique has problems (see alarmfunc).
613      This should be the last thing we do before calling Curl_resolv(),
614      as otherwise we'd have to worry about variables that get modified
615      before we invoke Curl_resolv() (and thus use "volatile"). */
616   if(sigsetjmp(curl_jmpenv, 1)) {
617     /* this is coming from a siglongjmp() after an alarm signal */
618     failf(data, "name lookup timed out");
619     rc = CURLRESOLV_ERROR;
620     goto clean_up;
621   }
622
623 #else
624 #ifndef CURLRES_ASYNCH
625   if(timeoutms)
626     infof(conn->data, "timeout on name lookup is not supported\n");
627 #else
628   (void)timeoutms; /* timeoutms not used with an async resolver */
629 #endif
630 #endif /* USE_ALARM_TIMEOUT */
631
632   /* Perform the actual name resolution. This might be interrupted by an
633    * alarm if it takes too long.
634    */
635   rc = Curl_resolv(conn, hostname, port, entry);
636
637 #ifdef USE_ALARM_TIMEOUT
638 clean_up:
639
640   if(!prev_alarm)
641     /* deactivate a possibly active alarm before uninstalling the handler */
642     alarm(0);
643
644 #ifdef HAVE_SIGACTION
645   if(keep_copysig) {
646     /* we got a struct as it looked before, now put that one back nice
647        and clean */
648     sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
649   }
650 #else
651 #ifdef HAVE_SIGNAL
652   /* restore the previous SIGALRM handler */
653   signal(SIGALRM, keep_sigact);
654 #endif
655 #endif /* HAVE_SIGACTION */
656
657   /* switch back the alarm() to either zero or to what it was before minus
658      the time we spent until now! */
659   if(prev_alarm) {
660     /* there was an alarm() set before us, now put it back */
661     unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);
662
663     /* the alarm period is counted in even number of seconds */
664     unsigned long alarm_set = prev_alarm - elapsed_ms/1000;
665
666     if(!alarm_set ||
667        ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
668       /* if the alarm time-left reached zero or turned "negative" (counted
669          with unsigned values), we should fire off a SIGALRM here, but we
670          won't, and zero would be to switch it off so we never set it to
671          less than 1! */
672       alarm(1);
673       rc = CURLRESOLV_TIMEDOUT;
674       failf(data, "Previous alarm fired off!");
675     }
676     else
677       alarm((unsigned int)alarm_set);
678   }
679 #endif /* USE_ALARM_TIMEOUT */
680
681   return rc;
682 }
683
684 /*
685  * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
686  * made, the struct may be destroyed due to pruning. It is important that only
687  * one unlock is made for each Curl_resolv() call.
688  *
689  * May be called with 'data' == NULL for global cache.
690  */
691 void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
692 {
693   DEBUGASSERT(dns && (dns->inuse>0));
694
695   if(data && data->share)
696     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
697
698   dns->inuse--;
699   /* only free if nobody is using AND it is not in hostcache (timestamp ==
700      0) */
701   if(dns->inuse == 0 && dns->timestamp == 0) {
702     Curl_freeaddrinfo(dns->addr);
703     free(dns);
704   }
705
706   if(data && data->share)
707     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
708 }
709
710 /*
711  * File-internal: free a cache dns entry.
712  */
713 static void freednsentry(void *freethis)
714 {
715   struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
716
717   /* mark the entry as not in hostcache */
718   p->timestamp = 0;
719   if(p->inuse == 0) {
720     Curl_freeaddrinfo(p->addr);
721     free(p);
722   }
723 }
724
725 /*
726  * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
727  */
728 struct curl_hash *Curl_mk_dnscache(void)
729 {
730   return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry);
731 }
732
733 static int hostcache_inuse(void *data, void *hc)
734 {
735   struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
736
737   if(c->inuse == 1)
738     Curl_resolv_unlock(data, c);
739
740   return 1; /* free all entries */
741 }
742
743 /*
744  * Curl_hostcache_clean()
745  *
746  * This _can_ be called with 'data' == NULL but then of course no locking
747  * can be done!
748  */
749
750 void Curl_hostcache_clean(struct SessionHandle *data,
751                           struct curl_hash *hash)
752 {
753   /* Entries added to the hostcache with the CURLOPT_RESOLVE function are
754    * still present in the cache with the inuse counter set to 1. Detect them
755    * and cleanup!
756    */
757   Curl_hash_clean_with_criterium(hash, data, hostcache_inuse);
758 }
759
760
761 CURLcode Curl_loadhostpairs(struct SessionHandle *data)
762 {
763   struct curl_slist *hostp;
764   char hostname[256];
765   char address[256];
766   int port;
767
768   for(hostp = data->change.resolve; hostp; hostp = hostp->next ) {
769     if(!hostp->data)
770       continue;
771     if(hostp->data[0] == '-') {
772       /* TODO: mark an entry for removal */
773     }
774     else if(3 == sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
775                         address)) {
776       struct Curl_dns_entry *dns;
777       Curl_addrinfo *addr;
778       char *entry_id;
779       size_t entry_len;
780
781       addr = Curl_str2addr(address, port);
782       if(!addr) {
783         infof(data, "Resolve %s found illegal!\n", hostp->data);
784         continue;
785       }
786
787       /* Create an entry id, based upon the hostname and port */
788       entry_id = create_hostcache_id(hostname, port);
789       /* If we can't create the entry id, fail */
790       if(!entry_id) {
791         Curl_freeaddrinfo(addr);
792         return CURLE_OUT_OF_MEMORY;
793       }
794
795       entry_len = strlen(entry_id);
796
797       if(data->share)
798         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
799
800       /* See if its already in our dns cache */
801       dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
802
803       /* free the allocated entry_id again */
804       free(entry_id);
805
806       if(!dns)
807         /* if not in the cache already, put this host in the cache */
808         dns = Curl_cache_addr(data, addr, hostname, port);
809       else
810         /* this is a duplicate, free it again */
811         Curl_freeaddrinfo(addr);
812
813       if(data->share)
814         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
815
816       if(!dns) {
817         Curl_freeaddrinfo(addr);
818         return CURLE_OUT_OF_MEMORY;
819       }
820       infof(data, "Added %s:%d:%s to DNS cache\n",
821             hostname, port, address);
822     }
823   }
824   data->change.resolve = NULL; /* dealt with now */
825
826   return CURLE_OK;
827 }