#include "setup.h"
#include <string.h>
#include <errno.h>
#define _REENTRANT
#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#include <winsock.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>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#include <stdlib.h>
#endif
#endif
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#include "urldata.h"
#include "sendf.h"
#include "hostip.h"
#include "hash.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
#include "inet_ntoa_r.h"
#endif
#ifdef MALLOCDEBUG
#include "memdebug.h"
#endif
static curl_hash hostname_cache;
static int host_cache_initialized;
static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data,
char *hostname,
int port,
char **bufp);
void Curl_global_host_cache_init(void)
{
if (!host_cache_initialized) {
Curl_hash_init(&hostname_cache, 7, Curl_freeaddrinfo);
host_cache_initialized = 1;
}
}
curl_hash *Curl_global_host_cache_get(void)
{
return &hostname_cache;
}
void Curl_global_host_cache_dtor(void)
{
if (host_cache_initialized) {
Curl_hash_clean(&hostname_cache);
host_cache_initialized = 0;
}
}
static int _num_chars(int i)
{
int chars = 0;
do {
chars++;
i = (int) i / 10;
} while (i >= 1);
return chars;
}
static char *
create_hostcache_id(char *server, int port, ssize_t *entry_len)
{
char *id = NULL;
*entry_len = *entry_len +
1 +
_num_chars(port);
id = malloc(*entry_len + 1);
if (!id) {
return NULL;
}
if (sprintf(id, "%s:%d", server, port) != *entry_len) {
*entry_len = 0;
free(id);
return NULL;
}
return id;
}
struct hostcache_prune_data {
int cache_timeout;
int now;
};
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) {
return 0;
}
return 1;
}
static void
hostcache_prune(curl_hash *hostcache, int cache_timeout, int 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);
}
#if defined(MALLOCDEBUG) && defined(AGGRESIVE_TEST)
void Curl_scan_cache_used(void *user, void *ptr)
{
struct Curl_dns_entry *e = ptr;
(void)user;
if(e->inuse) {
fprintf(stderr, "*** WARNING: locked DNS cache entry detected: %s\n",
e->entry_id);
*(void **)0 = 0;
}
}
#endif
#define HOSTCACHE_RETURN(dns) \
{ \
free(entry_id); \
return dns; \
}
#ifdef HAVE_SIGSETJMP
sigjmp_buf curl_jmpenv;
#endif
struct Curl_dns_entry *Curl_resolv(struct SessionHandle *data,
char *hostname,
int port)
{
char *entry_id = NULL;
struct Curl_dns_entry *dns = NULL;
ssize_t entry_len;
time_t now;
char *bufp;
#ifdef HAVE_SIGSETJMP
if(!data->set.no_signal && sigsetjmp(curl_jmpenv, 1)) {
failf(data, "name lookup time-outed");
return NULL;
}
#endif
entry_len = strlen(hostname);
entry_id = create_hostcache_id(hostname, port, &entry_len);
if (!entry_id)
return NULL;
dns = Curl_hash_pick(data->hostcache, entry_id, entry_len+1);
if (!dns) {
Curl_addrinfo *addr = my_getaddrinfo(data, hostname, port, &bufp);
if (!addr) {
HOSTCACHE_RETURN(NULL);
}
dns = (struct Curl_dns_entry *) malloc(sizeof(struct Curl_dns_entry));
if (!dns) {
Curl_freeaddrinfo(addr);
HOSTCACHE_RETURN(NULL);
}
dns->inuse = 0;
dns->addr = addr;
Curl_hash_add(data->hostcache, entry_id, entry_len+1, (const void *) dns);
}
time(&now);
dns->timestamp = now;
dns->inuse++;
#ifdef MALLOCDEBUG
dns->entry_id = entry_id;
#endif
hostcache_prune(data->hostcache,
data->set.dns_cache_timeout,
now);
HOSTCACHE_RETURN(dns);
}
void Curl_freeaddrinfo(void *freethis)
{
struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
#ifdef ENABLE_IPV6
freeaddrinfo(p->addr);
#else
free(p->addr);
#endif
free(p);
}
#ifdef ENABLE_IPV6
#ifdef MALLOCDEBUG
int curl_getaddrinfo(char *hostname, char *service,
struct addrinfo *hints,
struct addrinfo **result,
int line, const char *source)
{
int res=(getaddrinfo)(hostname, service, hints, result);
if(0 == res) {
if(logfile)
fprintf(logfile, "ADDR %s:%d getaddrinfo() = %p\n",
source, line, (void *)*result);
}
else {
if(logfile)
fprintf(logfile, "ADDR %s:%d getaddrinfo() failed\n",
source, line);
}
return res;
}
void curl_freeaddrinfo(struct addrinfo *freethis,
int line, const char *source)
{
(freeaddrinfo)(freethis);
if(logfile)
fprintf(logfile, "ADDR %s:%d freeaddrinfo(%p)\n",
source, line, (void *)freethis);
}
#endif
static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data,
char *hostname,
int port,
char **bufp)
{
struct addrinfo hints, *res;
int error;
char sbuf[NI_MAXSERV];
int s, pf = PF_UNSPEC;
s = socket(PF_INET6, SOCK_DGRAM, 0);
if (s < 0)
pf = PF_INET;
else
sclose(s);
memset(&hints, 0, sizeof(hints));
hints.ai_family = pf;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
snprintf(sbuf, sizeof(sbuf), "%d", port);
error = getaddrinfo(hostname, sbuf, &hints, &res);
if (error) {
infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port);
return NULL;
}
*bufp=(char *)res;
return res;
}
#else
#ifndef HAVE_GETHOSTBYNAME_R
static void hostcache_fixoffset(struct hostent *h, int offset);
static struct hostent* pack_hostent(char** buf, struct hostent* orig)
{
char *bufptr;
char *newbuf;
struct hostent* copy;
int i;
char *str;
int len;
bufptr = *buf;
copy = (struct hostent*)bufptr;
bufptr += sizeof(struct hostent);
copy->h_name = bufptr;
len = strlen(orig->h_name) + 1;
strncpy(bufptr, orig->h_name, len);
bufptr += len;
#define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7)))
bufptr = MEMALIGN(bufptr);
copy->h_aliases = (char**)bufptr;
for (i = 0; orig->h_aliases[i] != NULL; ++i);
bufptr += (i + 1) * sizeof(char*);
for(i = 0; (str = orig->h_aliases[i]); i++) {
len = strlen(str) + 1;
strncpy(bufptr, str, len);
copy->h_aliases[i] = bufptr;
bufptr += len;
}
copy->h_aliases[i] = NULL;
copy->h_addrtype = orig->h_addrtype;
copy->h_length = orig->h_length;
bufptr = MEMALIGN(bufptr);
copy->h_addr_list = (char**)bufptr;
for (i = 0; orig->h_addr_list[i] != NULL; ++i);
bufptr += (i + 1) * sizeof(char*);
i = 0;
len = orig->h_length;
str = orig->h_addr_list[i];
while (str != NULL) {
memcpy(bufptr, str, len);
copy->h_addr_list[i] = bufptr;
bufptr += len;
str = orig->h_addr_list[++i];
}
copy->h_addr_list[i] = NULL;
newbuf=(char *)realloc(*buf, (int)bufptr-(int)(*buf));
if(newbuf != *buf)
hostcache_fixoffset((struct hostent*)newbuf, (int)newbuf-(int)*buf);
*buf = newbuf;
copy = (struct hostent*)newbuf;
return copy;
}
#endif
static char *MakeIP(unsigned long num,char *addr, int addr_len)
{
#if defined(HAVE_INET_NTOA) || defined(HAVE_INET_NTOA_R)
struct in_addr in;
in.s_addr = htonl(num);
#if defined(HAVE_INET_NTOA_R)
inet_ntoa_r(in,addr,addr_len);
#else
strncpy(addr,inet_ntoa(in),addr_len);
#endif
#else
unsigned char *paddr;
num = htonl(num);
paddr = (unsigned char *)#
sprintf(addr, "%u.%u.%u.%u", paddr[0], paddr[1], paddr[2], paddr[3]);
#endif
return (addr);
}
#ifndef INADDR_NONE
#define INADDR_NONE (in_addr_t) ~0
#endif
static void hostcache_fixoffset(struct hostent *h, int offset)
{
int i=0;
h->h_name=(char *)((long)h->h_name+offset);
h->h_aliases=(char **)((long)h->h_aliases+offset);
while(h->h_aliases[i]) {
h->h_aliases[i]=(char *)((long)h->h_aliases[i]+offset);
i++;
}
h->h_addr_list=(char **)((long)h->h_addr_list+offset);
i=0;
while(h->h_addr_list[i]) {
h->h_addr_list[i]=(char *)((long)h->h_addr_list[i]+offset);
i++;
}
}
static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data,
char *hostname,
int port,
char **bufp)
{
struct hostent *h = NULL;
in_addr_t in;
int ret;
#define CURL_NAMELOOKUP_SIZE 9000
port=0;
ret = 0;
if ( (in=inet_addr(hostname)) != INADDR_NONE ) {
struct in_addr *addrentry;
struct namebuf {
struct hostent hostentry;
char *h_addr_list[2];
struct in_addr addrentry;
char h_name[128];
} *buf = (struct namebuf *)malloc(sizeof(struct namebuf));
if(!buf)
return NULL;
*bufp = (char *)buf;
h = &buf->hostentry;
h->h_addr_list = &buf->h_addr_list[0];
addrentry = &buf->addrentry;
addrentry->s_addr = in;
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];
MakeIP(ntohl(in), h->h_name, sizeof(buf->h_name));
}
#if defined(HAVE_GETHOSTBYNAME_R)
else {
int h_errnop;
int res=ERANGE;
int step_size=200;
int *buf = (int *)malloc(CURL_NAMELOOKUP_SIZE);
if(!buf)
return NULL;
*bufp=(char *)buf;
memset(buf, 0, CURL_NAMELOOKUP_SIZE);
#ifdef HAVE_GETHOSTBYNAME_R_5
(void)res;
while(!h) {
h = gethostbyname_r(hostname,
(struct hostent *)buf,
(char *)buf + sizeof(struct hostent),
step_size - sizeof(struct hostent),
&h_errnop);
if(h || (errno != ERANGE))
break;
step_size+=200;
}
#ifdef MALLOCDEBUG
infof(data, "gethostbyname_r() uses %d bytes\n", step_size);
#endif
if(h) {
int offset;
h=(struct hostent *)realloc(buf, step_size);
offset=(long)h-(long)buf;
hostcache_fixoffset(h, offset);
buf=(int *)h;
*bufp=(char *)buf;
}
else
#endif
#ifdef HAVE_GETHOSTBYNAME_R_6
do {
res=gethostbyname_r(hostname,
(struct hostent *)buf,
(char *)buf + sizeof(struct hostent),
step_size - sizeof(struct hostent),
&h,
&h_errnop);
if((ERANGE == res) || (EAGAIN == res)) {
step_size+=200;
continue;
}
break;
} while(1);
if(!h)
res=1;
#ifdef MALLOCDEBUG
infof(data, "gethostbyname_r() uses %d bytes\n", step_size);
#endif
if(!res) {
int offset;
h=(struct hostent *)realloc(buf, step_size);
offset=(long)h-(long)buf;
hostcache_fixoffset(h, offset);
buf=(int *)h;
*bufp=(char *)buf;
}
else
#endif
#ifdef HAVE_GETHOSTBYNAME_R_3
if(CURL_NAMELOOKUP_SIZE >=
(sizeof(struct hostent)+sizeof(struct hostent_data)))
ret = gethostbyname_r(hostname,
(struct hostent *)buf,
(struct hostent_data *)((char *)buf + sizeof(struct hostent)));
else
ret = -1;
h = (struct hostent*)buf;
h_errnop= errno;
if(ret)
#endif
{
infof(data, "gethostbyname_r(2) failed for %s\n", hostname);
h = NULL;
free(buf);
*bufp=NULL;
}
#else
else {
if ((h = gethostbyname(hostname)) == NULL ) {
infof(data, "gethostbyname(2) failed for %s\n", hostname);
*bufp=NULL;
}
else
{
char *buf=(char *)malloc(CURL_NAMELOOKUP_SIZE);
h = pack_hostent(&buf, h);
*bufp=(char *)buf;
}
#endif
}
return (h);
}
#endif