| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 1998 - 2007, Daniel Stenberg, <[email protected]>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://curl.haxx.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| * $Id$ |
| ***************************************************************************/ |
| |
| #include "setup.h" |
| |
| #include <string.h> |
| |
| #ifdef NEED_MALLOC_H |
| #include <malloc.h> |
| #endif |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| #ifdef HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| #ifdef HAVE_NETDB_H |
| #include <netdb.h> |
| #endif |
| #ifdef HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> /* required for free() prototypes */ |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> /* for the close() proto */ |
| #endif |
| #ifdef VMS |
| #include <in.h> |
| #include <inet.h> |
| #include <stdlib.h> |
| #endif |
| |
| #ifdef HAVE_SETJMP_H |
| #include <setjmp.h> |
| #endif |
| |
| #ifdef HAVE_PROCESS_H |
| #include <process.h> |
| #endif |
| |
| #include "urldata.h" |
| #include "sendf.h" |
| #include "hostip.h" |
| #include "hash.h" |
| #include "share.h" |
| #include "strerror.h" |
| #include "url.h" |
| #include "inet_ntop.h" |
| |
| #define _MPRINTF_REPLACE /* use our functions only */ |
| #include <curl/mprintf.h> |
| |
| #if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) |
| #include "inet_ntoa_r.h" |
| #endif |
| |
| #include "memory.h" |
| /* The last #include file should be: */ |
| #include "memdebug.h" |
| |
| /* |
| * hostip.c explained |
| * ================== |
| * |
| * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c |
| * source file are these: |
| * |
| * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use |
| * that. The host may not be able to resolve IPv6, but we don't really have to |
| * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 |
| * defined. |
| * |
| * CURLRES_ARES - is defined if libcurl is built to use c-ares for |
| * asynchronous name resolves. This can be Windows or *nix. |
| * |
| * CURLRES_THREADED - is defined if libcurl is built to run under (native) |
| * Windows, and then the name resolve will be done in a new thread, and the |
| * supported API will be the same as for ares-builds. |
| * |
| * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If |
| * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is |
| * defined. |
| * |
| * The host*.c sources files are split up like this: |
| * |
| * hostip.c - method-independent resolver functions and utility functions |
| * hostasyn.c - functions for asynchronous name resolves |
| * hostsyn.c - functions for synchronous name resolves |
| * hostares.c - functions for ares-using name resolves |
| * hostthre.c - functions for threaded name resolves |
| * hostip4.c - ipv4-specific functions |
| * hostip6.c - ipv6-specific functions |
| * |
| * The hostip.h is the united header file for all this. It defines the |
| * CURLRES_* defines based on the config*.h and setup.h defines. |
| */ |
| |
| /* These two symbols are for the global DNS cache */ |
| static struct curl_hash hostname_cache; |
| static int host_cache_initialized; |
| |
| static void freednsentry(void *freethis); |
| |
| /* |
| * Curl_global_host_cache_init() initializes and sets up a global DNS cache. |
| * Global DNS cache is general badness. Do not use. This will be removed in |
| * a future version. Use the share interface instead! |
| */ |
| void Curl_global_host_cache_init(void) |
| { |
| if (!host_cache_initialized) { |
| Curl_hash_init(&hostname_cache, 7, Curl_hash_str, Curl_str_key_compare, |
| freednsentry); |
| host_cache_initialized = 1; |
| } |
| } |
| |
| /* |
| * Return a pointer to the global cache |
| */ |
| struct curl_hash *Curl_global_host_cache_get(void) |
| { |
| return &hostname_cache; |
| } |
| |
| /* |
| * Destroy and cleanup the global DNS cache |
| */ |
| void Curl_global_host_cache_dtor(void) |
| { |
| if (host_cache_initialized) { |
| Curl_hash_clean(&hostname_cache); |
| host_cache_initialized = 0; |
| } |
| } |
| |
| /* |
| * Return # of adresses in a Curl_addrinfo struct |
| */ |
| int Curl_num_addresses(const Curl_addrinfo *addr) |
| { |
| int i; |
| for (i = 0; addr; addr = addr->ai_next, i++) |
| ; /* empty loop */ |
| return i; |
| } |
| |
| /* |
| * Curl_printable_address() returns a printable version of the 1st address |
| * given in the 'ip' argument. The result will be stored in the buf that is |
| * bufsize bytes big. |
| * |
| * If the conversion fails, it returns NULL. |
| */ |
| const char *Curl_printable_address(const Curl_addrinfo *ip, |
| char *buf, size_t bufsize) |
| { |
| const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr; |
| int af = ip->ai_family; |
| #ifdef CURLRES_IPV6 |
| const void *ip6 = &((const struct sockaddr_in6*)ip->ai_addr)->sin6_addr; |
| #else |
| const void *ip6 = NULL; |
| #endif |
| |
| return Curl_inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize); |
| } |
| |
| /* |
| * Return a hostcache id string for the providing host + port, to be used by |
| * the DNS caching. |
| */ |
| static char * |
| create_hostcache_id(const char *server, int port) |
| { |
| /* create and return the new allocated entry */ |
| return aprintf("%s:%d", server, port); |
| } |
| |
| struct hostcache_prune_data { |
| int cache_timeout; |
| time_t now; |
| }; |
| |
| /* |
| * This function is set as a callback to be called for every entry in the DNS |
| * cache when we want to prune old unused entries. |
| * |
| * Returning non-zero means remove the entry, return 0 to keep it in the |
| * cache. |
| */ |
| static int |
| hostcache_timestamp_remove(void *datap, void *hc) |
| { |
| struct hostcache_prune_data *data = |
| (struct hostcache_prune_data *) datap; |
| struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; |
| |
| if ((data->now - c->timestamp < data->cache_timeout) || |
| c->inuse) { |
| /* please don't remove */ |
| return 0; |
| } |
| |
| /* fine, remove */ |
| return 1; |
| } |
| |
| /* |
| * Prune the DNS cache. This assumes that a lock has already been taken. |
| */ |
| static void |
| hostcache_prune(struct curl_hash *hostcache, int cache_timeout, time_t now) |
| { |
| struct hostcache_prune_data user; |
| |
| user.cache_timeout = cache_timeout; |
| user.now = now; |
| |
| Curl_hash_clean_with_criterium(hostcache, |
| (void *) &user, |
| hostcache_timestamp_remove); |
| } |
| |
| /* |
| * Library-wide function for pruning the DNS cache. This function takes and |
| * returns the appropriate locks. |
| */ |
| void Curl_hostcache_prune(struct SessionHandle *data) |
| { |
| time_t now; |
| |
| if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache) |
| /* cache forever means never prune, and NULL hostcache means |
| we can't do it */ |
| return; |
| |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| |
| time(&now); |
| |
| /* Remove outdated and unused entries from the hostcache */ |
| hostcache_prune(data->dns.hostcache, |
| data->set.dns_cache_timeout, |
| now); |
| |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| } |
| |
| static int |
| remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns) |
| { |
| struct hostcache_prune_data user; |
| |
| if( !dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache) |
| /* cache forever means never prune, and NULL hostcache means |
| we can't do it */ |
| return 0; |
| |
| time(&user.now); |
| user.cache_timeout = data->set.dns_cache_timeout; |
| |
| if ( !hostcache_timestamp_remove(&user,dns) ) |
| return 0; |
| |
| /* ok, we do need to clear the cache. although we need to remove just a |
| single entry we clean the entire hash, as no explicit delete function |
| is provided */ |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| |
| Curl_hash_clean_with_criterium(data->dns.hostcache, |
| (void *) &user, |
| hostcache_timestamp_remove); |
| |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| |
| return 1; |
| } |
| |
| |
| #ifdef HAVE_SIGSETJMP |
| /* Beware this is a global and unique instance. This is used to store the |
| return address that we can jump back to from inside a signal handler. This |
| is not thread-safe stuff. */ |
| sigjmp_buf curl_jmpenv; |
| #endif |
| |
| |
| /* |
| * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. |
| * |
| * When calling Curl_resolv() has resulted in a response with a returned |
| * address, we call this function to store the information in the dns |
| * cache etc |
| * |
| * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. |
| */ |
| struct Curl_dns_entry * |
| Curl_cache_addr(struct SessionHandle *data, |
| Curl_addrinfo *addr, |
| const char *hostname, |
| int port) |
| { |
| char *entry_id; |
| size_t entry_len; |
| struct Curl_dns_entry *dns; |
| struct Curl_dns_entry *dns2; |
| time_t now; |
| |
| /* Create an entry id, based upon the hostname and port */ |
| entry_id = create_hostcache_id(hostname, port); |
| /* If we can't create the entry id, fail */ |
| if (!entry_id) |
| return NULL; |
| entry_len = strlen(entry_id); |
| |
| /* Create a new cache entry */ |
| dns = (struct Curl_dns_entry *) calloc(sizeof(struct Curl_dns_entry), 1); |
| if (!dns) { |
| free(entry_id); |
| return NULL; |
| } |
| |
| dns->inuse = 0; /* init to not used */ |
| dns->addr = addr; /* this is the address(es) */ |
| |
| /* Store the resolved data in our DNS cache. This function may return a |
| pointer to an existing struct already present in the hash, and it may |
| return the same argument we pass in. Make no assumptions. */ |
| dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1, |
| (void *)dns); |
| if(!dns2) { |
| /* Major badness, run away. */ |
| free(dns); |
| free(entry_id); |
| return NULL; |
| } |
| time(&now); |
| dns = dns2; |
| |
| dns->timestamp = now; /* used now */ |
| dns->inuse++; /* mark entry as in-use */ |
| |
| /* free the allocated entry_id again */ |
| free(entry_id); |
| |
| return dns; |
| } |
| |
| /* |
| * Curl_resolv() is the main name resolve function within libcurl. It resolves |
| * a name and returns a pointer to the entry in the 'entry' argument (if one |
| * is provided). This function might return immediately if we're using asynch |
| * resolves. See the return codes. |
| * |
| * The cache entry we return will get its 'inuse' counter increased when this |
| * function is used. You MUST call Curl_resolv_unlock() later (when you're |
| * done using this struct) to decrease the counter again. |
| * |
| * Return codes: |
| * |
| * CURLRESOLV_ERROR (-1) = error, no pointer |
| * CURLRESOLV_RESOLVED (0) = OK, pointer provided |
| * CURLRESOLV_PENDING (1) = waiting for response, no pointer |
| */ |
| |
| int Curl_resolv(struct connectdata *conn, |
| const char *hostname, |
| int port, |
| struct Curl_dns_entry **entry) |
| { |
| char *entry_id = NULL; |
| struct Curl_dns_entry *dns = NULL; |
| size_t entry_len; |
| int wait; |
| struct SessionHandle *data = conn->data; |
| CURLcode result; |
| int rc; |
| *entry = NULL; |
| |
| #ifdef HAVE_SIGSETJMP |
| /* this allows us to time-out from the name resolver, as the timeout |
| will generate a signal and we will siglongjmp() from that here */ |
| if(!data->set.no_signal) { |
| if (sigsetjmp(curl_jmpenv, 1)) { |
| /* this is coming from a siglongjmp() */ |
| failf(data, "name lookup timed out"); |
| return CURLRESOLV_ERROR; |
| } |
| } |
| #endif |
| |
| /* Create an entry id, based upon the hostname and port */ |
| entry_id = create_hostcache_id(hostname, port); |
| /* If we can't create the entry id, fail */ |
| if (!entry_id) |
| return CURLRESOLV_ERROR; |
| |
| entry_len = strlen(entry_id); |
| |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| |
| /* See if its already in our dns cache */ |
| dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); |
| |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| |
| /* free the allocated entry_id again */ |
| free(entry_id); |
| |
| /* See whether the returned entry is stale. Deliberately done after the |
| locked block */ |
| if ( remove_entry_if_stale(data,dns) ) |
| dns = NULL; /* the memory deallocation is being handled by the hash */ |
| |
| rc = CURLRESOLV_ERROR; /* default to failure */ |
| |
| if (!dns) { |
| /* The entry was not in the cache. Resolve it to IP address */ |
| |
| Curl_addrinfo *addr; |
| |
| /* Check what IP specifics the app has requested and if we can provide it. |
| * If not, bail out. */ |
| if(!Curl_ipvalid(data)) |
| return CURLRESOLV_ERROR; |
| |
| /* If Curl_getaddrinfo() returns NULL, 'wait' might be set to a non-zero |
| value indicating that we need to wait for the response to the resolve |
| call */ |
| addr = Curl_getaddrinfo(conn, hostname, port, &wait); |
| |
| if (!addr) { |
| if(wait) { |
| /* the response to our resolve call will come asynchronously at |
| a later time, good or bad */ |
| /* First, check that we haven't received the info by now */ |
| result = Curl_is_resolved(conn, &dns); |
| if(result) /* error detected */ |
| return CURLRESOLV_ERROR; |
| if(dns) |
| rc = CURLRESOLV_RESOLVED; /* pointer provided */ |
| else |
| rc = CURLRESOLV_PENDING; /* no info yet */ |
| } |
| } |
| else { |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| |
| /* we got a response, store it in the cache */ |
| dns = Curl_cache_addr(data, addr, hostname, port); |
| |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| |
| if(!dns) |
| /* returned failure, bail out nicely */ |
| Curl_freeaddrinfo(addr); |
| else |
| rc = CURLRESOLV_RESOLVED; |
| } |
| } |
| else { |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| dns->inuse++; /* we use it! */ |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| rc = CURLRESOLV_RESOLVED; |
| } |
| |
| *entry = dns; |
| |
| return rc; |
| } |
| |
| /* |
| * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been |
| * made, the struct may be destroyed due to pruning. It is important that only |
| * one unlock is made for each Curl_resolv() call. |
| */ |
| void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns) |
| { |
| DEBUGASSERT(dns && (dns->inuse>0)); |
| |
| if(data->share) |
| Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| |
| dns->inuse--; |
| |
| if(data->share) |
| Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| } |
| |
| /* |
| * File-internal: free a cache dns entry. |
| */ |
| static void freednsentry(void *freethis) |
| { |
| struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis; |
| |
| Curl_freeaddrinfo(p->addr); |
| |
| free(p); |
| } |
| |
| /* |
| * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it. |
| */ |
| struct curl_hash *Curl_mk_dnscache(void) |
| { |
| return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry); |
| } |
| |
| #ifdef CURLRES_ADDRINFO_COPY |
| |
| /* align on even 64bit boundaries */ |
| #define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7))) |
| |
| /* |
| * Curl_addrinfo_copy() performs a "deep" copy of a hostent into a buffer and |
| * returns a pointer to the malloc()ed copy. You need to call free() on the |
| * returned buffer when you're done with it. |
| */ |
| Curl_addrinfo *Curl_addrinfo_copy(const void *org, int port) |
| { |
| const struct hostent *orig = org; |
| |
| return Curl_he2ai(orig, port); |
| } |
| #endif /* CURLRES_ADDRINFO_COPY */ |
| |
| /*********************************************************************** |
| * Only for plain-ipv4 and c-ares builds |
| **********************************************************************/ |
| |
| #if defined(CURLRES_IPV4) || defined(CURLRES_ARES) |
| /* |
| * This is a function for freeing name information in a protocol independent |
| * way. |
| */ |
| void Curl_freeaddrinfo(Curl_addrinfo *ai) |
| { |
| Curl_addrinfo *next; |
| |
| /* walk over the list and free all entries */ |
| while(ai) { |
| next = ai->ai_next; |
| if(ai->ai_canonname) |
| free(ai->ai_canonname); |
| free(ai); |
| ai = next; |
| } |
| } |
| |
| struct namebuf { |
| struct hostent hostentry; |
| char *h_addr_list[2]; |
| struct in_addr addrentry; |
| char h_name[16]; /* 123.123.123.123 = 15 letters is maximum */ |
| }; |
| |
| /* |
| * Curl_ip2addr() takes a 32bit ipv4 internet address as input parameter |
| * together with a pointer to the string version of the address, and it |
| * returns a Curl_addrinfo chain filled in correctly with information for this |
| * address/host. |
| * |
| * The input parameters ARE NOT checked for validity but they are expected |
| * to have been checked already when this is called. |
| */ |
| Curl_addrinfo *Curl_ip2addr(in_addr_t num, const char *hostname, int port) |
| { |
| Curl_addrinfo *ai; |
| |
| #if defined(VMS) && \ |
| defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) |
| #pragma pointer_size save |
| #pragma pointer_size short |
| #pragma message disable PTRMISMATCH |
| #endif |
| |
| struct hostent *h; |
| struct in_addr *addrentry; |
| struct namebuf buffer; |
| struct namebuf *buf = &buffer; |
| |
| h = &buf->hostentry; |
| h->h_addr_list = &buf->h_addr_list[0]; |
| addrentry = &buf->addrentry; |
| #ifdef _CRAYC |
| /* On UNICOS, s_addr is a bit field and for some reason assigning to it |
| * doesn't work. There must be a better fix than this ugly hack. |
| */ |
| memcpy(addrentry, &num, SIZEOF_in_addr); |
| #else |
| addrentry->s_addr = num; |
| #endif |
| h->h_addr_list[0] = (char*)addrentry; |
| h->h_addr_list[1] = NULL; |
| h->h_addrtype = AF_INET; |
| h->h_length = sizeof(*addrentry); |
| h->h_name = &buf->h_name[0]; |
| h->h_aliases = NULL; |
| |
| /* Now store the dotted version of the address */ |
| snprintf((char *)h->h_name, 16, "%s", hostname); |
| |
| #if defined(VMS) && \ |
| defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) |
| #pragma pointer_size restore |
| #pragma message enable PTRMISMATCH |
| #endif |
| |
| ai = Curl_he2ai(h, port); |
| |
| return ai; |
| } |
| #endif /* CURLRES_IPV4 || CURLRES_ARES */ |
| |
| |