#include "curl_setup.h"
#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 __VMS
#include <in.h>
#include <inet.h>
#endif
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.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"
#include "warnless.h"
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#if defined(CURLRES_SYNCH) && \
defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
#define USE_ALARM_TIMEOUT
#endif
static struct curl_hash hostname_cache;
static int host_cache_initialized;
static void freednsentry(void *freethis);
struct curl_hash *Curl_global_host_cache_init(void)
{
int rc = 0;
if(!host_cache_initialized) {
rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
Curl_str_key_compare, freednsentry);
if(!rc)
host_cache_initialized = 1;
}
return rc?NULL:&hostname_cache;
}
void Curl_global_host_cache_dtor(void)
{
if(host_cache_initialized) {
Curl_hash_destroy(&hostname_cache);
host_cache_initialized = 0;
}
}
int Curl_num_addresses(const Curl_addrinfo *addr)
{
int i = 0;
while(addr) {
addr = addr->ai_next;
i++;
}
return i;
}
const char *
Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize)
{
const struct sockaddr_in *sa4;
const struct in_addr *ipaddr4;
#ifdef ENABLE_IPV6
const struct sockaddr_in6 *sa6;
const struct in6_addr *ipaddr6;
#endif
switch (ai->ai_family) {
case AF_INET:
sa4 = (const void *)ai->ai_addr;
ipaddr4 = &sa4->sin_addr;
return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf,
bufsize);
#ifdef ENABLE_IPV6
case AF_INET6:
sa6 = (const void *)ai->ai_addr;
ipaddr6 = &sa6->sin6_addr;
return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf,
bufsize);
#endif
default:
break;
}
return NULL;
}
static char *
create_hostcache_id(const char *name, int port)
{
char *id = aprintf("%s:%d", name, port);
char *ptr = id;
if(ptr) {
while(*ptr && (*ptr != ':')) {
*ptr = (char)TOLOWER(*ptr);
ptr++;
}
}
return id;
}
struct hostcache_prune_data {
long cache_timeout;
time_t 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;
return (0 != c->timestamp)
&& (data->now - c->timestamp >= data->cache_timeout);
}
static void
hostcache_prune(struct curl_hash *hostcache, long 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);
}
void Curl_hostcache_prune(struct Curl_easy *data)
{
time_t now;
if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
return;
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
time(&now);
hostcache_prune(data->dns.hostcache,
data->set.dns_cache_timeout,
now);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
#ifdef HAVE_SIGSETJMP
sigjmp_buf curl_jmpenv;
#endif
static struct Curl_dns_entry *
fetch_addr(struct connectdata *conn,
const char *hostname,
int port)
{
char *entry_id = NULL;
struct Curl_dns_entry *dns = NULL;
size_t entry_len;
struct Curl_easy *data = conn->data;
entry_id = create_hostcache_id(hostname, port);
if(!entry_id)
return dns;
entry_len = strlen(entry_id);
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
if(dns && (data->set.dns_cache_timeout != -1)) {
struct hostcache_prune_data user;
time(&user.now);
user.cache_timeout = data->set.dns_cache_timeout;
if(hostcache_timestamp_remove(&user, dns)) {
infof(data, "Hostname in DNS cache was stale, zapped\n");
dns = NULL;
Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1);
}
}
free(entry_id);
return dns;
}
struct Curl_dns_entry *
Curl_fetch_addr(struct connectdata *conn,
const char *hostname,
int port)
{
struct Curl_easy *data = conn->data;
struct Curl_dns_entry *dns = NULL;
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns = fetch_addr(conn, hostname, port);
if(dns)
dns->inuse++;
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
return dns;
}
struct Curl_dns_entry *
Curl_cache_addr(struct Curl_easy *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;
entry_id = create_hostcache_id(hostname, port);
if(!entry_id)
return NULL;
entry_len = strlen(entry_id);
dns = calloc(1, sizeof(struct Curl_dns_entry));
if(!dns) {
free(entry_id);
return NULL;
}
dns->inuse = 1;
dns->addr = addr;
time(&dns->timestamp);
if(dns->timestamp == 0)
dns->timestamp = 1;
dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
(void *)dns);
if(!dns2) {
free(dns);
free(entry_id);
return NULL;
}
dns = dns2;
dns->inuse++;
free(entry_id);
return dns;
}
int Curl_resolv(struct connectdata *conn,
const char *hostname,
int port,
struct Curl_dns_entry **entry)
{
struct Curl_dns_entry *dns = NULL;
struct Curl_easy *data = conn->data;
CURLcode result;
int rc = CURLRESOLV_ERROR;
*entry = NULL;
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns = fetch_addr(conn, hostname, port);
if(dns) {
infof(data, "Hostname %s was found in DNS cache\n", hostname);
dns->inuse++;
rc = CURLRESOLV_RESOLVED;
}
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns) {
Curl_addrinfo *addr;
int respwait;
if(!Curl_ipvalid(conn))
return CURLRESOLV_ERROR;
addr = Curl_getaddrinfo(conn,
#ifdef DEBUGBUILD
(data->set.str[STRING_DEVICE]
&& !strcmp(data->set.str[STRING_DEVICE],
"LocalHost"))?"localhost":
#endif
hostname, port, &respwait);
if(!addr) {
if(respwait) {
result = Curl_resolver_is_resolved(conn, &dns);
if(result)
return CURLRESOLV_ERROR;
if(dns)
rc = CURLRESOLV_RESOLVED;
else
rc = CURLRESOLV_PENDING;
}
}
else {
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns = Curl_cache_addr(data, addr, hostname, port);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns)
Curl_freeaddrinfo(addr);
else
rc = CURLRESOLV_RESOLVED;
}
}
*entry = dns;
return rc;
}
#ifdef USE_ALARM_TIMEOUT
static
RETSIGTYPE alarmfunc(int sig)
{
(void)sig;
siglongjmp(curl_jmpenv, 1);
return;
}
#endif
int Curl_resolv_timeout(struct connectdata *conn,
const char *hostname,
int port,
struct Curl_dns_entry **entry,
long timeoutms)
{
#ifdef USE_ALARM_TIMEOUT
#ifdef HAVE_SIGACTION
struct sigaction keep_sigact;
volatile bool keep_copysig = FALSE;
struct sigaction sigact;
#else
#ifdef HAVE_SIGNAL
void (*keep_sigact)(int);
#endif
#endif
volatile long timeout;
volatile unsigned int prev_alarm = 0;
struct Curl_easy *data = conn->data;
#endif
int rc;
*entry = NULL;
if(timeoutms < 0)
return CURLRESOLV_TIMEDOUT;
#ifdef USE_ALARM_TIMEOUT
if(data->set.no_signal)
timeout = 0;
else
timeout = timeoutms;
if(!timeout)
return Curl_resolv(conn, hostname, port, entry);
if(timeout < 1000) {
failf(data,
"remaining timeout of %ld too small to resolve via SIGALRM method",
timeout);
return CURLRESOLV_TIMEDOUT;
}
if(sigsetjmp(curl_jmpenv, 1)) {
failf(data, "name lookup timed out");
rc = CURLRESOLV_ERROR;
goto clean_up;
}
else {
#ifdef HAVE_SIGACTION
sigaction(SIGALRM, NULL, &sigact);
keep_sigact = sigact;
keep_copysig = TRUE;
sigact.sa_handler = alarmfunc;
#ifdef SA_RESTART
sigact.sa_flags &= ~SA_RESTART;
#endif
sigaction(SIGALRM, &sigact, NULL);
#else
#ifdef HAVE_SIGNAL
keep_sigact = signal(SIGALRM, alarmfunc);
#endif
#endif
prev_alarm = alarm(curlx_sltoui(timeout/1000L));
}
#else
#ifndef CURLRES_ASYNCH
if(timeoutms)
infof(conn->data, "timeout on name lookup is not supported\n");
#else
(void)timeoutms;
#endif
#endif
rc = Curl_resolv(conn, hostname, port, entry);
#ifdef USE_ALARM_TIMEOUT
clean_up:
if(!prev_alarm)
alarm(0);
#ifdef HAVE_SIGACTION
if(keep_copysig) {
sigaction(SIGALRM, &keep_sigact, NULL);
}
#else
#ifdef HAVE_SIGNAL
signal(SIGALRM, keep_sigact);
#endif
#endif
if(prev_alarm) {
unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);
unsigned long alarm_set = prev_alarm - elapsed_ms/1000;
if(!alarm_set ||
((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
alarm(1);
rc = CURLRESOLV_TIMEDOUT;
failf(data, "Previous alarm fired off!");
}
else
alarm((unsigned int)alarm_set);
}
#endif
return rc;
}
void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns)
{
if(data && data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
freednsentry(dns);
if(data && data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
static void freednsentry(void *freethis)
{
struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
DEBUGASSERT(dns && (dns->inuse>0));
dns->inuse--;
if(dns->inuse == 0) {
Curl_freeaddrinfo(dns->addr);
free(dns);
}
}
int Curl_mk_dnscache(struct curl_hash *hash)
{
return Curl_hash_init(hash, 7, Curl_hash_str, Curl_str_key_compare,
freednsentry);
}
void Curl_hostcache_clean(struct Curl_easy *data,
struct curl_hash *hash)
{
if(data && data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Curl_hash_clean(hash);
if(data && data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
CURLcode Curl_loadhostpairs(struct Curl_easy *data)
{
struct curl_slist *hostp;
char hostname[256];
char address[256];
int port;
for(hostp = data->change.resolve; hostp; hostp = hostp->next) {
if(!hostp->data)
continue;
if(hostp->data[0] == '-') {
char *entry_id;
size_t entry_len;
if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) {
infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'!\n",
hostp->data);
continue;
}
entry_id = create_hostcache_id(hostname, port);
if(!entry_id) {
return CURLE_OUT_OF_MEMORY;
}
entry_len = strlen(entry_id);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
free(entry_id);
}
else {
struct Curl_dns_entry *dns;
Curl_addrinfo *addr;
char *entry_id;
size_t entry_len;
if(3 != sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
address)) {
infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
hostp->data);
continue;
}
addr = Curl_str2addr(address, port);
if(!addr) {
infof(data, "Address in '%s' found illegal!\n", hostp->data);
continue;
}
entry_id = create_hostcache_id(hostname, port);
if(!entry_id) {
Curl_freeaddrinfo(addr);
return CURLE_OUT_OF_MEMORY;
}
entry_len = strlen(entry_id);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
free(entry_id);
if(!dns) {
dns = Curl_cache_addr(data, addr, hostname, port);
if(dns) {
dns->timestamp = 0;
dns->inuse--;
}
}
else
Curl_freeaddrinfo(addr);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns) {
Curl_freeaddrinfo(addr);
return CURLE_OUT_OF_MEMORY;
}
infof(data, "Added %s:%d:%s to DNS cache\n",
hostname, port, address);
}
}
data->change.resolve = NULL;
return CURLE_OK;
}