diff options
Diffstat (limited to 'Utilities/cmcurl/hostip.c')
-rw-r--r-- | Utilities/cmcurl/hostip.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/Utilities/cmcurl/hostip.c b/Utilities/cmcurl/hostip.c new file mode 100644 index 0000000..43ade26 --- /dev/null +++ b/Utilities/cmcurl/hostip.c @@ -0,0 +1,540 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, 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> +#include <errno.h> + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include <malloc.h> +#else +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.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 +#endif + +#ifdef HAVE_SETJMP_H +#include <setjmp.h> +#endif + +#ifdef WIN32 +#include <process.h> +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#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 "curl_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. It cannot have ENABLE_IPV6 defined at the same + * time, as c-ares has no ipv6 support. 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 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, freednsentry); + host_cache_initialized = 1; + } +} + +/* + * Return a pointer to the global cache + */ +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++); + return i; +} + +#define GET_SIN_ADDR_FROM_CURL_ADDRINFO(ai_addr, si, sin, sinaddr, ip) \ + { \ + union { \ + struct si* vsi; \ + struct sin* vsin;\ + } vi; \ + vi.vsi = ai_addr; \ + ip = &(vi.vsin->sinaddr); \ + } + +/* + * 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) +{ + int af = ip->ai_family; + const void *ip4; +#ifdef CURLRES_IPV6 + const void *ip6; + GET_SIN_ADDR_FROM_CURL_ADDRINFO(ip->ai_addr, sockaddr, sockaddr_in6, + sin6_addr, ip6); +#else + const void *ip6 = NULL; +#endif + GET_SIN_ADDR_FROM_CURL_ADDRINFO(ip->ai_addr, sockaddr, sockaddr_in, + sin_addr, ip4); + + 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(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(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) + /* cache forever means never prune! */ + 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->hostcache, + data->set.dns_cache_timeout, + now); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + +#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, + 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 *) malloc(sizeof(struct Curl_dns_entry)); + 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->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, + char *hostname, + int port, + struct Curl_dns_entry **entry) +{ + char *entry_id; + struct Curl_dns_entry *dns = NULL; + size_t entry_len; + int wait; + struct SessionHandle *data = conn->data; + CURLcode result; + + /* default to failure */ + 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 && sigsetjmp(curl_jmpenv, 1)) { + /* this is coming from a siglongjmp() */ + failf(data, "name lookup timed out"); + return CURLRESOLV_ERROR; + } +#endif + rc = CURLRESOLV_ERROR; + + /* 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->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); + + 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 { + dns->inuse++; /* we use it! */ + 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) +{ + curlassert(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. + */ +curl_hash *Curl_mk_dnscache(void) +{ + return Curl_hash_alloc(7, 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(void *org, int port) +{ + struct hostent *orig = org; + + return Curl_he2ai(orig, port); +} +#endif /* CURLRES_ADDRINFO_COPY */ |