Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* Copyright (C) 1998 - 2008, 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.
***************************************************************************/
#ifdef NEED_MALLOC_H
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <stdlib.h> /* required for free() prototypes */
Daniel Stenberg
committed
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for the close() proto */
#endif
#include <inet.h>
#include <stdlib.h>
#endif
Daniel Stenberg
committed
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#include <process.h>
#endif
#include "hash.h"
#include "strerror.h"
Daniel Stenberg
committed
#include "url.h"
Daniel Stenberg
committed
#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 "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 */
Daniel Stenberg
committed
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!
*
Daniel Stenberg
committed
* Returns a struct curl_hash pointer on success, NULL on failure.
*/
Daniel Stenberg
committed
struct curl_hash *Curl_global_host_cache_init(void)
int rc = 0;
rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
Curl_str_key_compare, freednsentry);
if(!rc)
host_cache_initialized = 1;
Daniel Stenberg
committed
return rc?NULL:&hostname_cache;
/*
* Destroy and cleanup the global DNS cache
*/
void Curl_global_host_cache_dtor(void)
{
Daniel Stenberg
committed
Curl_hash_clean(&hostname_cache);
host_cache_initialized = 0;
}
}
Daniel Stenberg
committed
/*
* Return # of adresses in a Curl_addrinfo struct
*/
int Curl_num_addresses(const Curl_addrinfo *addr)
{
int i;
Daniel Stenberg
committed
for (i = 0; addr; addr = addr->ai_next, i++)
; /* empty loop */
Daniel Stenberg
committed
return i;
Daniel Stenberg
committed
}
/*
* 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.
Daniel Stenberg
committed
*
* If the conversion fails, it returns NULL.
*/
Daniel Stenberg
committed
const char *Curl_printable_address(const Curl_addrinfo *ip,
Daniel Stenberg
committed
char *buf, size_t bufsize)
{
const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr;
int af = ip->ai_family;
Daniel Stenberg
committed
#ifdef CURLRES_IPV6
const void *ip6 = &((const struct sockaddr_in6*)ip->ai_addr)->sin6_addr;
Daniel Stenberg
committed
#else
Daniel Stenberg
committed
const void *ip6 = NULL;
Daniel Stenberg
committed
#endif
Daniel Stenberg
committed
return Curl_inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize);
Daniel Stenberg
committed
}
/*
* Return a hostcache id string for the providing host + port, to be used by
* the DNS caching.
*/
create_hostcache_id(const char *server, int port)
/* create and return the new allocated entry */
return aprintf("%s:%d", server, port);
Daniel Stenberg
committed
struct hostcache_prune_data {
/*
* 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.
*/
hostcache_timestamp_remove(void *datap, void *hc)
Daniel Stenberg
committed
(struct hostcache_prune_data *) datap;
struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
c->inuse) {
/* please don't remove */
/* fine, remove */
/*
* Prune the DNS cache. This assumes that a lock has already been taken.
*/
hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
Daniel Stenberg
committed
if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
Daniel Stenberg
committed
/* cache forever means never prune, and NULL hostcache means
we can't do it */
Daniel Stenberg
committed
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 */
Daniel Stenberg
committed
hostcache_prune(data->dns.hostcache,
data->set.dns_cache_timeout,
now);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
/*
* Check if the entry should be pruned. Assumes a locked cache.
*/
Daniel Stenberg
committed
static int
remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
{
struct hostcache_prune_data user;
Daniel Stenberg
committed
if( !dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
Daniel Stenberg
committed
/* 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;
Daniel Stenberg
committed
return 0;
Daniel Stenberg
committed
Curl_hash_clean_with_criterium(data->dns.hostcache,
Daniel Stenberg
committed
(void *) &user,
hostcache_timestamp_remove);
return 1;
}
Daniel Stenberg
committed
#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. */
Daniel Stenberg
committed
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)
{
Daniel Stenberg
committed
char *entry_id;
Daniel Stenberg
committed
size_t entry_len;
Daniel Stenberg
committed
struct Curl_dns_entry *dns;
struct Curl_dns_entry *dns2;
Daniel Stenberg
committed
time_t now;
/* Create an entry id, based upon the hostname and port */
entry_id = create_hostcache_id(hostname, port);
Daniel Stenberg
committed
/* If we can't create the entry id, fail */
Daniel Stenberg
committed
return NULL;
entry_len = strlen(entry_id);
Daniel Stenberg
committed
/* Create a new cache entry */
dns = (struct Curl_dns_entry *) calloc(sizeof(struct Curl_dns_entry), 1);
Daniel Stenberg
committed
free(entry_id);
return NULL;
}
Daniel Stenberg
committed
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. */
Daniel Stenberg
committed
dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
(void *)dns);
if(!dns2) {
/* Major badness, run away. */
free(dns);
Daniel Stenberg
committed
free(entry_id);
return NULL;
}
Daniel Stenberg
committed
time(&now);
dns = dns2;
Daniel Stenberg
committed
Daniel Stenberg
committed
dns->timestamp = now; /* used now */
Daniel Stenberg
committed
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:
*
Daniel Stenberg
committed
* CURLRESOLV_ERROR (-1) = error, no pointer
* CURLRESOLV_RESOLVED (0) = OK, pointer provided
* CURLRESOLV_PENDING (1) = waiting for response, no pointer
*/
Daniel Stenberg
committed
Daniel Stenberg
committed
int Curl_resolv(struct connectdata *conn,
const char *hostname,
Daniel Stenberg
committed
int port,
struct Curl_dns_entry **entry)
char *entry_id = NULL;
struct Curl_dns_entry *dns = NULL;
Daniel Stenberg
committed
size_t entry_len;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
CURLcode result;
int rc = CURLRESOLV_ERROR; /* default to failure */
Daniel Stenberg
committed
*entry = NULL;
Sterling Hughes
committed
Daniel Stenberg
committed
#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) {
/* this is coming from a siglongjmp() */
failf(data, "name lookup timed out");
}
Daniel Stenberg
committed
}
#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 */
entry_len = strlen(entry_id);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Sterling Hughes
committed
/* See if its already in our dns cache */
Daniel Stenberg
committed
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
/* See whether the returned entry is stale. Done before we release lock */
if( remove_entry_if_stale(data, dns) )
dns = NULL; /* the memory deallocation is being handled by the hash */
if(dns) {
dns->inuse++; /* we use it! */
rc = CURLRESOLV_RESOLVED;
}
Daniel Stenberg
committed
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
Sterling Hughes
committed
Daniel Stenberg
committed
/* free the allocated entry_id again */
free(entry_id);
Daniel Stenberg
committed
/* The entry was not in the cache. Resolve it to IP address */
Curl_addrinfo *addr;
int respwait;
/* Check what IP specifics the app has requested and if we can provide it.
* If not, bail out. */
if(!Curl_ipvalid(data))
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
/* If Curl_getaddrinfo() returns NULL, 'respwait' 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, &respwait);
if(respwait) {
/* the response to our resolve call will come asynchronously at
Daniel Stenberg
committed
a later time, good or bad */
Daniel Stenberg
committed
/* First, check that we haven't received the info by now */
result = Curl_is_resolved(conn, &dns);
if(result) /* error detected */
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
Daniel Stenberg
committed
if(dns)
Daniel Stenberg
committed
rc = CURLRESOLV_RESOLVED; /* pointer provided */
Daniel Stenberg
committed
else
Daniel Stenberg
committed
rc = CURLRESOLV_PENDING; /* no info yet */
Daniel Stenberg
committed
}
}
else {
Daniel Stenberg
committed
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Daniel Stenberg
committed
/* we got a response, store it in the cache */
dns = Curl_cache_addr(data, addr, hostname, port);
Daniel Stenberg
committed
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns)
/* returned failure, bail out nicely */
Curl_freeaddrinfo(addr);
Daniel Stenberg
committed
rc = CURLRESOLV_RESOLVED;
Daniel Stenberg
committed
}
}
Sterling Hughes
committed
Daniel Stenberg
committed
*entry = dns;
Daniel Stenberg
committed
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);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
Daniel Stenberg
committed
/*
* File-internal: free a cache dns entry.
Daniel Stenberg
committed
*/
static void freednsentry(void *freethis)
Daniel Stenberg
committed
{
struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
Curl_freeaddrinfo(p->addr);
Sterling Hughes
committed
free(p);
}
/*
* Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
*/
Daniel Stenberg
committed
struct curl_hash *Curl_mk_dnscache(void)
{
Daniel Stenberg
committed
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)))
Daniel Stenberg
committed
/*
* 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.
Daniel Stenberg
committed
*/
Curl_addrinfo *Curl_addrinfo_copy(const void *org, int port)
Daniel Stenberg
committed
{
const struct hostent *orig = org;
Daniel Stenberg
committed
Daniel Stenberg
committed
return Curl_he2ai(orig, port);
Daniel Stenberg
committed
}
#endif /* CURLRES_ADDRINFO_COPY */
/***********************************************************************
Daniel Stenberg
committed
* Only for plain-ipv4 and c-ares builds (NOTE: c-ares builds can be IPv6
* enabled)
**********************************************************************/
#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;
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(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;
}
Daniel Stenberg
committed
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
/*
* Curl_he2ai() translates from a hostent struct to a Curl_addrinfo struct.
* The Curl_addrinfo is meant to work like the addrinfo struct does for IPv6
* stacks, but for all hosts and environments.
*
* Curl_addrinfo defined in "lib/hostip.h"
*
* struct Curl_addrinfo {
* int ai_flags;
* int ai_family;
* int ai_socktype;
* int ai_protocol;
* socklen_t ai_addrlen; * Follow rfc3493 struct addrinfo *
* char *ai_canonname;
* struct sockaddr *ai_addr;
* struct Curl_addrinfo *ai_next;
* };
*
* hostent defined in <netdb.h>
*
* struct hostent {
* char *h_name;
* char **h_aliases;
* int h_addrtype;
* int h_length;
* char **h_addr_list;
* };
*
* for backward compatibility:
*
* #define h_addr h_addr_list[0]
*/
Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port)
{
Curl_addrinfo *ai;
Curl_addrinfo *prevai = NULL;
Curl_addrinfo *firstai = NULL;
struct sockaddr_in *addr;
#ifdef CURLRES_IPV6
struct sockaddr_in6 *addr6;
#endif /* CURLRES_IPV6 */
int i;
struct in_addr *curr;
if(!he)
/* no input == no output! */
return NULL;
for(i=0; (curr = (struct in_addr *)he->h_addr_list[i]) != NULL; i++) {
int ss_size;
#ifdef CURLRES_IPV6
if (he->h_addrtype == AF_INET6)
ss_size = sizeof (struct sockaddr_in6);
else
#endif /* CURLRES_IPV6 */
ss_size = sizeof (struct sockaddr_in);
Daniel Stenberg
committed
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
ai = calloc(1, sizeof(Curl_addrinfo) + ss_size);
if(!ai)
break;
if(!firstai)
/* store the pointer we want to return from this function */
firstai = ai;
if(prevai)
/* make the previous entry point to this */
prevai->ai_next = ai;
ai->ai_family = he->h_addrtype;
/* we return all names as STREAM, so when using this address for TFTP
the type must be ignored and conn->socktype be used instead! */
ai->ai_socktype = SOCK_STREAM;
ai->ai_addrlen = ss_size;
/* make the ai_addr point to the address immediately following this struct
and use that area to store the address */
ai->ai_addr = (struct sockaddr *) ((char*)ai + sizeof(Curl_addrinfo));
/* need to free this eventually */
ai->ai_canonname = strdup(he->h_name);
/* leave the rest of the struct filled with zero */
switch (ai->ai_family) {
case AF_INET:
addr = (struct sockaddr_in *)ai->ai_addr; /* storage area for this info */
memcpy((char *)&(addr->sin_addr), curr, sizeof(struct in_addr));
addr->sin_family = (unsigned short)(he->h_addrtype);
addr->sin_port = htons((unsigned short)port);
break;
#ifdef CURLRES_IPV6
case AF_INET6:
addr6 = (struct sockaddr_in6 *)ai->ai_addr; /* storage area for this info */
memcpy((char *)&(addr6->sin6_addr), curr, sizeof(struct in6_addr));
addr6->sin6_family = (unsigned short)(he->h_addrtype);
addr6->sin6_port = htons((unsigned short)port);
break;
#endif /* CURLRES_IPV6 */
}
prevai = ai;
}
return firstai;
}
#endif /* CURLRES_IPV4 || CURLRES_ARES */