832 lines
21 KiB
C
832 lines
21 KiB
C
/* dns.c
|
|
* (c) 2002 Mikulas Patocka
|
|
* This file is a part of the Links program, released under GPL
|
|
*/
|
|
|
|
#include "links.h"
|
|
|
|
#ifdef SUPPORT_IPV6
|
|
int support_ipv6;
|
|
#endif
|
|
|
|
#if !defined(USE_GETADDRINFO) && (defined(HAVE_GETHOSTBYNAME_BUG) || !defined(HAVE_GETHOSTBYNAME))
|
|
#define EXTERNAL_LOOKUP
|
|
#endif
|
|
|
|
#if defined(WIN) || defined(INTERIX)
|
|
#define EXTRA_IPV6_LOOKUP
|
|
#endif
|
|
|
|
#ifdef OPENVMS_64BIT
|
|
/* 64-bit getaddrinfo with _SOCKADDR_LEN is broken and returns garbage */
|
|
#undef addrinfo
|
|
#undef getaddrinfo
|
|
#undef freeaddrinfo
|
|
#define addrinfo __addrinfo32
|
|
static int my_getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **res)
|
|
{
|
|
int r;
|
|
#pragma __pointer_size 32
|
|
char *host_32;
|
|
struct addrinfo hints_32;
|
|
struct addrinfo *res_32;
|
|
#pragma __pointer_size 64
|
|
|
|
host_32 = _malloc32(strlen(host) + 1);
|
|
if (!host_32) {
|
|
errno = ENOMEM;
|
|
return EAI_SYSTEM;
|
|
}
|
|
strcpy(host_32, host);
|
|
memcpy(&hints_32, hints, sizeof(struct addrinfo));
|
|
|
|
r = __getaddrinfo32(host_32, NULL, &hints_32, &res_32);
|
|
|
|
free(host_32);
|
|
|
|
if (!r)
|
|
*res = res_32;
|
|
|
|
return r;
|
|
}
|
|
static void my_freeaddrinfo(struct addrinfo *res)
|
|
{
|
|
#pragma __pointer_size 32
|
|
struct addrinfo *res_32 = (struct addrinfo *)res;
|
|
#pragma __pointer_size 64
|
|
__freeaddrinfo32(res_32);
|
|
}
|
|
#define getaddrinfo my_getaddrinfo
|
|
#define freeaddrinfo my_freeaddrinfo
|
|
#endif
|
|
|
|
struct dnsentry {
|
|
list_entry_1st
|
|
uttime absolute_time;
|
|
uttime timeout;
|
|
struct lookup_result addr;
|
|
list_entry_last
|
|
unsigned char name[1];
|
|
};
|
|
|
|
#ifndef THREAD_SAFE_LOOKUP
|
|
struct dnsquery *dns_queue = NULL;
|
|
#endif
|
|
|
|
static int dns_cache_addr_preference = -1;
|
|
static struct list_head dns_cache = {&dns_cache, &dns_cache};
|
|
|
|
static int shrink_dns_cache(int u);
|
|
|
|
static int get_addr_byte(unsigned char **ptr, unsigned char *res, unsigned char stp)
|
|
{
|
|
unsigned u = 0;
|
|
if (!(**ptr >= '0' && **ptr <= '9')) return -1;
|
|
while (**ptr >= '0' && **ptr <= '9') {
|
|
u = u * 10 + **ptr - '0';
|
|
if (u >= 256) return -1;
|
|
(*ptr)++;
|
|
}
|
|
if (stp != 255 && **ptr != stp) return -1;
|
|
(*ptr)++;
|
|
*res = (unsigned char)u;
|
|
return 0;
|
|
}
|
|
|
|
int numeric_ip_address(unsigned char *name, unsigned char address[4])
|
|
{
|
|
unsigned char dummy[4];
|
|
if (!address) address = dummy;
|
|
if (get_addr_byte(&name, address + 0, '.')) return -1;
|
|
if (get_addr_byte(&name, address + 1, '.')) return -1;
|
|
if (get_addr_byte(&name, address + 2, '.')) return -1;
|
|
if (get_addr_byte(&name, address + 3, 0)) return -1;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SUPPORT_IPV6
|
|
|
|
static int extract_ipv6_address(struct addrinfo *p, unsigned char address[16], unsigned *scope_id)
|
|
{
|
|
/*{
|
|
int i;
|
|
for (i = 0; i < p->ai_addrlen; i++)
|
|
fprintf(stderr, "%02x%c", ((unsigned char *)p->ai_addr)[i], i != p->ai_addrlen - 1 ? ':' : '\n');
|
|
}*/
|
|
if (p->ai_family == AF_INET6 && (socklen_t)p->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6) && p->ai_addr->sa_family == AF_INET6) {
|
|
memcpy(address, &((struct sockaddr_in6 *)p->ai_addr)->sin6_addr, 16);
|
|
#ifdef SUPPORT_IPV6_SCOPE
|
|
*scope_id = ((struct sockaddr_in6 *)p->ai_addr)->sin6_scope_id;
|
|
#else
|
|
*scope_id = 0;
|
|
#endif
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int numeric_ipv6_address(unsigned char *name, unsigned char address[16], unsigned *scope_id)
|
|
{
|
|
unsigned char dummy_a[16];
|
|
unsigned dummy_s;
|
|
int r;
|
|
#ifdef HAVE_INET_PTON
|
|
struct in6_addr i6a;
|
|
#endif
|
|
struct addrinfo hints, *res;
|
|
if (!address) address = dummy_a;
|
|
if (!scope_id) scope_id = &dummy_s;
|
|
|
|
#ifdef HAVE_INET_PTON
|
|
if (inet_pton(AF_INET6, cast_const_char name, &i6a) == 1) {
|
|
memcpy(address, &i6a, 16);
|
|
*scope_id = 0;
|
|
return 0;
|
|
}
|
|
if (!strchr(cast_const_char name, '%'))
|
|
return -1;
|
|
#endif
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_INET6;
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
if (getaddrinfo(cast_const_char name, NULL, &hints, &res))
|
|
return -1;
|
|
r = extract_ipv6_address(res, address, scope_id);
|
|
freeaddrinfo(res);
|
|
return r;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef EXTERNAL_LOOKUP
|
|
|
|
static int do_external_lookup(unsigned char *name, unsigned char *host)
|
|
{
|
|
unsigned char buffer[1024];
|
|
unsigned char sink[16];
|
|
int rd;
|
|
int pi[2];
|
|
pid_t f;
|
|
unsigned char *n;
|
|
int rs;
|
|
if (c_pipe(pi) == -1)
|
|
return -1;
|
|
EINTRLOOP(f, fork());
|
|
if (f == -1) {
|
|
EINTRLOOP(rs, close(pi[0]));
|
|
EINTRLOOP(rs, close(pi[1]));
|
|
return -1;
|
|
}
|
|
if (!f) {
|
|
#ifdef HAVE_SETSID
|
|
/* without setsid it gets stuck when on background */
|
|
EINTRLOOP(rs, setsid());
|
|
#endif
|
|
EINTRLOOP(rs, close(pi[0]));
|
|
EINTRLOOP(rs, dup2(pi[1], 1));
|
|
if (rs == -1) _exit(1);
|
|
EINTRLOOP(rs, dup2(pi[1], 2));
|
|
if (rs == -1) _exit(1);
|
|
EINTRLOOP(rs, close(pi[1]));
|
|
EINTRLOOP(rs, execlp("host", "host", cast_const_char name, (char *)NULL));
|
|
EINTRLOOP(rs, execl("/usr/sbin/host", "host", cast_const_char name, (char *)NULL));
|
|
_exit(1);
|
|
}
|
|
EINTRLOOP(rs, close(pi[1]));
|
|
rd = hard_read(pi[0], buffer, sizeof buffer - 1);
|
|
if (rd >= 0) buffer[rd] = 0;
|
|
if (rd > 0) {
|
|
while (hard_read(pi[0], sink, sizeof sink) > 0);
|
|
}
|
|
EINTRLOOP(rs, close(pi[0]));
|
|
/* Don't wait for the process, we already have sigchld handler that
|
|
* does cleanup.
|
|
* waitpid(f, NULL, 0); */
|
|
if (rd < 0) return -1;
|
|
/*fprintf(stderr, "query: '%s', result: %s\n", name, buffer);*/
|
|
while ((n = strstr(buffer, name))) {
|
|
memset(n, '-', strlen(cast_const_char name));
|
|
}
|
|
for (n = buffer; n < buffer + rd; n++) {
|
|
if (*n >= '0' && *n <= '9') {
|
|
if (get_addr_byte(&n, host + 0, '.')) goto skip_addr;
|
|
if (get_addr_byte(&n, host + 1, '.')) goto skip_addr;
|
|
if (get_addr_byte(&n, host + 2, '.')) goto skip_addr;
|
|
if (get_addr_byte(&n, host + 3, 255)) goto skip_addr;
|
|
return 0;
|
|
skip_addr:
|
|
if (n >= buffer + rd) break;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int memcmp_host_address(struct host_address *a, struct host_address *b)
|
|
{
|
|
if (a->af != b->af || a->scope_id != b->scope_id)
|
|
return 1;
|
|
return memcmp(a->addr, b->addr, sizeof a->addr);
|
|
}
|
|
|
|
void add_address(struct lookup_result *host, int af, unsigned char *address, unsigned scope_id, int preference)
|
|
{
|
|
struct host_address neww;
|
|
struct host_address *e, *t;
|
|
struct host_address *n;
|
|
if (af != AF_INET && preference == ADDR_PREFERENCE_IPV4_ONLY)
|
|
return;
|
|
#ifdef SUPPORT_IPV6
|
|
if (af != AF_INET6 && preference == ADDR_PREFERENCE_IPV6_ONLY)
|
|
return;
|
|
#endif
|
|
if (host->n >= MAX_ADDRESSES)
|
|
return;
|
|
memset(&neww, 0, sizeof(struct host_address));
|
|
neww.af = af;
|
|
memcpy(neww.addr, address, af == AF_INET ? 4 : 16);
|
|
neww.scope_id = scope_id;
|
|
e = &host->a[host->n];
|
|
t = e;
|
|
for (n = host->a; n != e; n++) {
|
|
if (!memcmp_host_address(n, &neww))
|
|
return;
|
|
if (preference == ADDR_PREFERENCE_IPV4 && af == AF_INET && n->af != AF_INET) {
|
|
t = n;
|
|
break;
|
|
}
|
|
#ifdef SUPPORT_IPV6
|
|
if (preference == ADDR_PREFERENCE_IPV6 && af == AF_INET6 && n->af != AF_INET6) {
|
|
t = n;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
memmove(t + 1, t, (e - t) * sizeof(struct host_address));
|
|
memcpy(t, &neww, sizeof(struct host_address));
|
|
host->n++;
|
|
}
|
|
|
|
#ifdef USE_GETADDRINFO
|
|
|
|
static int use_getaddrinfo(unsigned char *name, struct addrinfo *hints, int preference, struct lookup_result *host)
|
|
{
|
|
int gai_err;
|
|
struct addrinfo *res, *p;
|
|
#ifdef OPENVMS
|
|
struct addrinfo default_hints;
|
|
if (!hints) {
|
|
memset(&default_hints, 0, sizeof default_hints);
|
|
default_hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
|
|
hints = &default_hints;
|
|
}
|
|
#endif
|
|
gai_err = getaddrinfo(cast_const_char name, NULL, hints, &res);
|
|
if (gai_err)
|
|
return gai_err;
|
|
for (p = res; p; p = p->ai_next) {
|
|
if (p->ai_family == AF_INET && (socklen_t)p->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in) && p->ai_addr->sa_family == AF_INET) {
|
|
add_address(host, AF_INET, (unsigned char *)&((struct sockaddr_in *)p->ai_addr)->sin_addr.s_addr, 0, preference);
|
|
continue;
|
|
}
|
|
#ifdef SUPPORT_IPV6
|
|
{
|
|
unsigned char address[16];
|
|
unsigned scope_id;
|
|
if (!extract_ipv6_address(p, address, &scope_id)) {
|
|
add_address(host, AF_INET6, address, scope_id, preference);
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
freeaddrinfo(res);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
void rotate_addresses(struct lookup_result *host)
|
|
{
|
|
int first_type, first_different, i;
|
|
|
|
if (host->n <= 2)
|
|
return;
|
|
|
|
first_type = host->a[0].af;
|
|
|
|
for (i = 1; i < host->n; i++) {
|
|
if (host->a[i].af != first_type) {
|
|
first_different = i;
|
|
goto do_swap;
|
|
}
|
|
}
|
|
return;
|
|
|
|
do_swap:
|
|
if (first_different > 1) {
|
|
struct host_address ha;
|
|
memcpy(&ha, &host->a[first_different], sizeof(struct host_address));
|
|
memmove(&host->a[2], &host->a[1], (first_different - 1) * sizeof(struct host_address));
|
|
memcpy(&host->a[1], &ha, sizeof(struct host_address));
|
|
}
|
|
}
|
|
|
|
void do_real_lookup(unsigned char *name, int preference, struct lookup_result *host)
|
|
{
|
|
unsigned char address[16];
|
|
#ifdef SUPPORT_IPV6
|
|
size_t nl;
|
|
#endif
|
|
|
|
memset(host, 0, sizeof(struct lookup_result));
|
|
|
|
if (strlen(cast_const_char name) >= 6 && !casestrcmp(name + strlen(cast_const_char name) - 6, cast_uchar ".onion"))
|
|
goto ret;
|
|
|
|
if (!support_ipv6) preference = ADDR_PREFERENCE_IPV4_ONLY;
|
|
|
|
if (!numeric_ip_address(name, address)) {
|
|
add_address(host, AF_INET, address, 0, preference);
|
|
goto ret;
|
|
}
|
|
#ifdef SUPPORT_IPV6
|
|
nl = strlen(cast_const_char name);
|
|
if (name[0] == '[' && name[nl - 1] == ']') {
|
|
unsigned char *n2 = cast_uchar strdup(cast_const_char(name + 1));
|
|
if (n2) {
|
|
unsigned scope_id;
|
|
n2[nl - 2] = 0;
|
|
if (!numeric_ipv6_address(n2, address, &scope_id)) {
|
|
free(n2);
|
|
add_address(host, AF_INET6, address, scope_id, preference);
|
|
goto ret;
|
|
}
|
|
free(n2);
|
|
}
|
|
} else {
|
|
unsigned scope_id;
|
|
if (!numeric_ipv6_address(name, address, &scope_id)) {
|
|
add_address(host, AF_INET6, address, scope_id, preference);
|
|
goto ret;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_GETADDRINFO)
|
|
use_getaddrinfo(name, NULL, preference, host);
|
|
#if defined(SUPPORT_IPV6) && defined(EXTRA_IPV6_LOOKUP)
|
|
if ((preference == ADDR_PREFERENCE_IPV4 && !host->n) ||
|
|
preference == ADDR_PREFERENCE_IPV6 ||
|
|
preference == ADDR_PREFERENCE_IPV6_ONLY) {
|
|
struct addrinfo hints;
|
|
int i;
|
|
for (i = 0; i < host->n; i++)
|
|
if (host->a[i].af == AF_INET6)
|
|
goto already_have_inet6;
|
|
memset(&hints, 0, sizeof hints);
|
|
hints.ai_family = AF_INET6;
|
|
hints.ai_flags = 0;
|
|
use_getaddrinfo(name, &hints, preference, host);
|
|
}
|
|
already_have_inet6:;
|
|
#endif
|
|
#elif defined(HAVE_GETHOSTBYNAME)
|
|
{
|
|
int i;
|
|
struct hostent *hst;
|
|
if ((hst = gethostbyname(cast_const_char name))) {
|
|
if (hst->h_addrtype != AF_INET || hst->h_length != 4 || !hst->h_addr)
|
|
goto ret;
|
|
#ifdef h_addr
|
|
for (i = 0; hst->h_addr_list[i]; i++) {
|
|
add_address(host, AF_INET, cast_uchar hst->h_addr_list[i], 0, preference);
|
|
}
|
|
#else
|
|
add_address(host, AF_INET, cast_uchar hst->h_addr, 0, preference);
|
|
#endif
|
|
goto ret;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef EXTERNAL_LOOKUP
|
|
if (!do_external_lookup(name, address)) {
|
|
add_address(host, AF_INET, address, 0, preference);
|
|
goto ret;
|
|
}
|
|
#endif
|
|
|
|
ret:
|
|
return;
|
|
}
|
|
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
static void lookup_fn(void *q_, int h)
|
|
{
|
|
struct dnsquery *q = (struct dnsquery *)q_;
|
|
struct lookup_result host;
|
|
do_real_lookup(q->name, q->addr_preference, &host);
|
|
/*{
|
|
int i;
|
|
for (i = 0; i < sizeof(struct lookup_result); i++) {
|
|
if (i == 1) portable_sleep(1000);
|
|
hard_write(h, (unsigned char *)&host + i, 1);
|
|
}
|
|
}*/
|
|
hard_write(h, (unsigned char *)&host, sizeof(struct lookup_result));
|
|
}
|
|
|
|
static void end_real_lookup(void *q_)
|
|
{
|
|
struct dnsquery *q = (struct dnsquery *)q_;
|
|
int r = 1;
|
|
int rs;
|
|
if (!q->addr || hard_read(q->h, (unsigned char *)q->addr, sizeof(struct lookup_result)) != sizeof(struct lookup_result) || !q->addr->n) goto end;
|
|
r = 0;
|
|
|
|
end:
|
|
set_handlers(q->h, NULL, NULL, NULL);
|
|
EINTRLOOP(rs, close(q->h));
|
|
end_dns_lookup(q, r, -1);
|
|
}
|
|
#endif
|
|
|
|
static void do_lookup(struct dnsquery *q, int force_async)
|
|
{
|
|
/*debug("starting lookup for %s", q->name);*/
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
if (!async_lookup && !force_async) {
|
|
#endif
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
sync_lookup:
|
|
#endif
|
|
do_real_lookup(q->name, q->addr_preference, q->addr);
|
|
end_dns_lookup(q, !q->addr->n, -1);
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
} else {
|
|
q->h = start_thread(lookup_fn, q, (int)((unsigned char *)strchr(cast_const_char q->name, 0) + 1 - (unsigned char *)q), 1);
|
|
if (q->h == -1) goto sync_lookup;
|
|
set_handlers(q->h, end_real_lookup, NULL, q);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void do_queued_lookup(struct dnsquery *q)
|
|
{
|
|
#ifndef THREAD_SAFE_LOOKUP
|
|
if (!dns_queue) {
|
|
dns_queue = q;
|
|
/*debug("direct lookup");*/
|
|
#endif
|
|
do_lookup(q, 0);
|
|
#ifndef THREAD_SAFE_LOOKUP
|
|
} else {
|
|
/*debug("queuing lookup for %s", q->name);*/
|
|
if (dns_queue->next_in_queue) internal_error("DNS queue corrupted");
|
|
dns_queue->next_in_queue = q;
|
|
dns_queue = q;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void check_dns_cache_addr_preference(void)
|
|
{
|
|
if (dns_cache_addr_preference != ipv6_options.addr_preference) {
|
|
shrink_dns_cache(SH_FREE_ALL);
|
|
dns_cache_addr_preference = ipv6_options.addr_preference;
|
|
}
|
|
}
|
|
|
|
static int find_in_dns_cache(unsigned char *name, struct dnsentry **dnsentry)
|
|
{
|
|
struct dnsentry *e;
|
|
struct list_head *le;
|
|
check_dns_cache_addr_preference();
|
|
foreach(struct dnsentry, e, le, dns_cache)
|
|
if (!casestrcmp(e->name, name)) {
|
|
del_from_list(e);
|
|
add_to_list(dns_cache, e);
|
|
*dnsentry = e;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void free_dns_entry(struct dnsentry *dnsentry)
|
|
{
|
|
del_from_list(dnsentry);
|
|
mem_free(dnsentry);
|
|
}
|
|
|
|
void end_dns_lookup(struct dnsquery *q, int a, uttime timeout)
|
|
{
|
|
struct dnsentry *dnsentry;
|
|
size_t sl;
|
|
void (*fn)(void *, int);
|
|
void *data;
|
|
/*debug("end lookup %s: %d, %lu", q->name, a, (unsigned long)timeout);*/
|
|
if (timeout > DNS_TIMEOUT)
|
|
timeout = DNS_TIMEOUT;
|
|
#ifndef THREAD_SAFE_LOOKUP
|
|
if (q->next_in_queue) {
|
|
/*debug("processing next in queue: %s", q->next_in_queue->name);*/
|
|
do_lookup(q->next_in_queue, 1);
|
|
} else dns_queue = NULL;
|
|
#endif
|
|
if (!q->fn || !q->addr) {
|
|
free(q);
|
|
return;
|
|
}
|
|
if (!find_in_dns_cache(q->name, &dnsentry)) {
|
|
if (a) {
|
|
memcpy(q->addr, &dnsentry->addr, sizeof(struct lookup_result));
|
|
a = 0;
|
|
goto e;
|
|
}
|
|
free_dns_entry(dnsentry);
|
|
}
|
|
if (a) goto e;
|
|
if (q->addr_preference != ipv6_options.addr_preference) goto e;
|
|
check_dns_cache_addr_preference();
|
|
sl = strlen(cast_const_char q->name);
|
|
if (sl > MAXINT - sizeof(struct dnsentry)) overalloc();
|
|
dnsentry = mem_alloc(sizeof(struct dnsentry) + sl);
|
|
strcpy(cast_char dnsentry->name, cast_const_char q->name);
|
|
memcpy(&dnsentry->addr, q->addr, sizeof(struct lookup_result));
|
|
dnsentry->absolute_time = get_absolute_time();
|
|
dnsentry->timeout = timeout;
|
|
add_to_list(dns_cache, dnsentry);
|
|
e:
|
|
if (q->s) *q->s = NULL;
|
|
fn = q->fn;
|
|
data = q->data;
|
|
free(q);
|
|
fn(data, a);
|
|
}
|
|
|
|
void find_host_no_cache(unsigned char *name, int no_doh, struct lookup_result *addr, void **qp, void (*fn)(void *, int), void *data)
|
|
{
|
|
struct dnsquery *q;
|
|
retry:
|
|
q = (struct dnsquery *)malloc(sizeof(struct dnsquery) + strlen(cast_const_char name));
|
|
if (!q) {
|
|
if (out_of_memory(0, NULL, 0))
|
|
goto retry;
|
|
fn(data, 1);
|
|
}
|
|
#ifndef THREAD_SAFE_LOOKUP
|
|
q->next_in_queue = NULL;
|
|
#endif
|
|
q->fn = fn;
|
|
q->data = data;
|
|
q->s = qp;
|
|
q->doh = NULL;
|
|
q->addr = addr;
|
|
q->addr_preference = ipv6_options.addr_preference;
|
|
strcpy(cast_char q->name, cast_const_char name);
|
|
if (qp) *qp = q;
|
|
if (is_noproxy_host(name))
|
|
no_doh = 1;
|
|
if (!numeric_ip_address(name, NULL))
|
|
no_doh = 1;
|
|
#ifdef SUPPORT_IPV6
|
|
if (!numeric_ipv6_address(name, NULL, NULL))
|
|
no_doh = 1;
|
|
#endif
|
|
if (!no_doh && *dns_over_https) {
|
|
do_doh_lookup(q);
|
|
} else {
|
|
do_queued_lookup(q);
|
|
}
|
|
}
|
|
|
|
int find_host_in_cache(unsigned char *name, struct lookup_result *addr, void **qp, void (*fn)(void *, int), void *data)
|
|
{
|
|
struct dnsentry *dnsentry;
|
|
if (qp) *qp = NULL;
|
|
if (!find_in_dns_cache(name, &dnsentry)) {
|
|
if (get_absolute_time() - dnsentry->absolute_time >= dnsentry->timeout) goto timeout;
|
|
memcpy(addr, &dnsentry->addr, sizeof(struct lookup_result));
|
|
fn(data, 0);
|
|
return 0;
|
|
}
|
|
timeout:
|
|
return -1;
|
|
}
|
|
|
|
void kill_dns_request(void **qp)
|
|
{
|
|
struct dnsquery *q = *qp;
|
|
q->fn = NULL;
|
|
q->addr = NULL;
|
|
*qp = NULL;
|
|
}
|
|
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
static void dns_prefetch_end(void *addr_, int status)
|
|
{
|
|
struct lookup_result *addr = (struct lookup_result *)addr_;
|
|
free(addr);
|
|
}
|
|
#endif
|
|
|
|
void dns_prefetch(unsigned char *name)
|
|
{
|
|
#ifndef NO_ASYNC_LOOKUP
|
|
struct lookup_result *addr;
|
|
if (!async_lookup)
|
|
return;
|
|
addr = (struct lookup_result *)malloc(sizeof(struct lookup_result));
|
|
if (!addr)
|
|
return;
|
|
if (find_host_in_cache(name, addr, NULL, dns_prefetch_end, addr))
|
|
find_host_no_cache(name, 0, addr, NULL, dns_prefetch_end, addr);
|
|
#endif
|
|
}
|
|
|
|
void dns_set_priority(unsigned char *name, struct host_address *address, int prefer)
|
|
{
|
|
int i;
|
|
struct dnsentry *dnsentry;
|
|
if (find_in_dns_cache(name, &dnsentry))
|
|
return;
|
|
for (i = 0; i < dnsentry->addr.n; i++)
|
|
if (!memcmp_host_address(&dnsentry->addr.a[i], address)) goto found_it;
|
|
return;
|
|
found_it:
|
|
if (prefer) {
|
|
memmove(&dnsentry->addr.a[1], &dnsentry->addr.a[0], i * sizeof(struct host_address));
|
|
memcpy(&dnsentry->addr.a[0], address, sizeof(struct host_address));
|
|
} else {
|
|
memmove(&dnsentry->addr.a[i], &dnsentry->addr.a[i + 1], (dnsentry->addr.n - i - 1) * sizeof(struct host_address));
|
|
memcpy(&dnsentry->addr.a[dnsentry->addr.n - 1], address, sizeof(struct host_address));
|
|
}
|
|
}
|
|
|
|
void dns_clear_host(unsigned char *name)
|
|
{
|
|
struct dnsentry *dnsentry;
|
|
if (find_in_dns_cache(name, &dnsentry))
|
|
return;
|
|
free_dns_entry(dnsentry);
|
|
}
|
|
|
|
unsigned long dns_info(int type)
|
|
{
|
|
switch (type) {
|
|
case CI_FILES:
|
|
shrink_dns_cache(SH_CHECK_QUOTA);
|
|
return list_size(&dns_cache);
|
|
default:
|
|
internal_error("dns_info: bad request");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int shrink_dns_cache(int u)
|
|
{
|
|
uttime now = get_absolute_time();
|
|
struct dnsentry *d;
|
|
struct list_head *ld;
|
|
int f = 0;
|
|
if (u == SH_FREE_SOMETHING && !list_empty(dns_cache)) {
|
|
d = list_struct(dns_cache.prev, struct dnsentry);
|
|
goto delete_last;
|
|
}
|
|
foreach(struct dnsentry, d, ld, dns_cache) if (u == SH_FREE_ALL || now - d->absolute_time >= d->timeout) {
|
|
delete_last:
|
|
ld = d->list_entry.prev;
|
|
free_dns_entry(d);
|
|
f = ST_SOMETHING_FREED;
|
|
}
|
|
return f | (list_empty(dns_cache) ? ST_CACHE_EMPTY : 0);
|
|
}
|
|
|
|
unsigned char *print_address(struct host_address *a)
|
|
{
|
|
#define SCOPE_ID_LEN 11
|
|
#ifdef SUPPORT_IPV6
|
|
static unsigned char buffer[INET6_ADDRSTRLEN + SCOPE_ID_LEN];
|
|
#else
|
|
static unsigned char buffer[INET_ADDRSTRLEN + SCOPE_ID_LEN];
|
|
#endif
|
|
#ifdef HAVE_INET_NTOP
|
|
union {
|
|
struct in_addr in;
|
|
#ifdef SUPPORT_IPV6
|
|
struct in6_addr in6;
|
|
#endif
|
|
char pad[16];
|
|
} u;
|
|
memcpy(&u, a->addr, 16);
|
|
if (!inet_ntop(a->af, &u, cast_char buffer, sizeof buffer - SCOPE_ID_LEN))
|
|
return NULL;
|
|
#else
|
|
if (a->af == AF_INET)
|
|
snprintf(cast_char buffer, sizeof buffer, "%d.%d.%d.%d", a->addr[0], a->addr[1], a->addr[2], a->addr[3]);
|
|
#ifdef SUPPORT_IPV6
|
|
else if (a->af == AF_INET6)
|
|
snprintf(cast_char buffer, sizeof buffer, "%x:%x:%x:%x:%x:%x:%x:%x",
|
|
(a->addr[0] << 8) | a->addr[1],
|
|
(a->addr[2] << 8) | a->addr[3],
|
|
(a->addr[4] << 8) | a->addr[5],
|
|
(a->addr[6] << 8) | a->addr[7],
|
|
(a->addr[8] << 8) | a->addr[9],
|
|
(a->addr[10] << 8) | a->addr[11],
|
|
(a->addr[12] << 8) | a->addr[13],
|
|
(a->addr[14] << 8) | a->addr[15]);
|
|
#endif
|
|
else
|
|
return NULL;
|
|
#endif
|
|
if (a->scope_id) {
|
|
unsigned char *end = cast_uchar strchr(cast_const_char buffer, 0);
|
|
snprintf(cast_char end, buffer + sizeof(buffer) - end, "%%%u", a->scope_id);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
int ipv6_full_access(void)
|
|
{
|
|
#ifdef SUPPORT_IPV6
|
|
/*
|
|
* Test if we can access global IPv6 address space.
|
|
* This doesn't send anything anywhere, it just creates an UDP socket,
|
|
* connects it and closes it.
|
|
*/
|
|
struct sockaddr_in6 sin6;
|
|
int h, c, rs;
|
|
if (!support_ipv6) return 0;
|
|
h = c_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (h == -1) return 0;
|
|
memset(&sin6, 0, sizeof sin6);
|
|
sin6.sin6_family = AF_INET6;
|
|
sin6.sin6_port = htons(1024);
|
|
memcpy(&sin6.sin6_addr.s6_addr, "\052\001\004\060\000\015\000\000\002\314\236\377\376\044\176\032", 16);
|
|
EINTRLOOP(c, connect(h, (struct sockaddr *)(void *)&sin6, sizeof sin6));
|
|
EINTRLOOP(rs, close(h));
|
|
if (!c) return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FLOOD_MEMORY
|
|
|
|
void flood_memory(void)
|
|
{
|
|
struct list_head list;
|
|
size_t s = 32768 * 32;
|
|
struct dnsentry *de;
|
|
dns_cache_addr_preference = ipv6_options.addr_preference;
|
|
#if defined(HAVE__HEAPMIN)
|
|
_heapmin();
|
|
#endif
|
|
init_list(list);
|
|
while (!list_empty(dns_cache)) {
|
|
de = list_struct(dns_cache.prev, struct dnsentry);
|
|
del_from_list(de);
|
|
add_to_list(list, de);
|
|
}
|
|
while (1) {
|
|
while ((de = mem_alloc_mayfail(s))) {
|
|
de->absolute_time = get_absolute_time();
|
|
de->timeout = DNS_TIMEOUT;
|
|
memset(&de->addr, 0, sizeof de->addr);
|
|
de->name[0] = 0;
|
|
add_to_list(list, de);
|
|
}
|
|
if (s == sizeof(struct dnsentry)) break;
|
|
s = s / 2;
|
|
if (s < sizeof(struct dnsentry)) s = sizeof(struct dnsentry);
|
|
}
|
|
while (!list_empty(list)) {
|
|
de = list_struct(list.prev, struct dnsentry);
|
|
del_from_list(de);
|
|
add_to_list(dns_cache, de);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void init_dns(void)
|
|
{
|
|
register_cache_upcall(shrink_dns_cache, 0, cast_uchar "dns");
|
|
#ifdef FLOOD_MEMORY
|
|
flood_memory();
|
|
#endif
|
|
#ifdef SUPPORT_IPV6
|
|
{
|
|
int h, rs;
|
|
h = c_socket(AF_INET6, SOCK_STREAM, 0);
|
|
if (h == -1) {
|
|
support_ipv6 = 0;
|
|
} else {
|
|
EINTRLOOP(rs, close(h));
|
|
support_ipv6 = 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|