"Fossies" - the Fresh Open Source Software archive

Member "links-1.03/dns.c" of archive links-1.03.tar.gz:


#include "links.h"

#if defined(HAVE_GETHOSTBYNAME_BUG) || !defined(HAVE_GETHOSTBYNAME)
#define EXTERNAL_LOOKUP
#endif

struct dnsentry {
	struct dnsentry *next;
	struct dnsentry *prev;
	ttime get_time;
	ip addr;
	unsigned char name[1];
};

#ifndef THREAD_SAFE_LOOKUP
struct dnsquery *dns_queue = NULL;
#endif

struct dnsquery {
#ifndef THREAD_SAFE_LOOKUP
	struct dnsquery *next_in_queue;
#endif
	void (*fn)(void *, int);
	void *data;
	void (*xfn)(struct dnsquery *, int);
	int h;
	struct dnsquery **s;
	ip *addr;
	unsigned char name[1];
};

struct list_head dns_cache = {&dns_cache, &dns_cache};

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 = u;
	return 0;
}

#ifdef EXTERNAL_LOOKUP

static int do_external_lookup(unsigned char *name, ip *host)
{
	unsigned char buffer[1024];
	unsigned char sink[16];
	int rd;
	int pi[2];
	pid_t f;
	unsigned char *n;
	if (pipe(pi) == -1)
		return -1;
	f = fork();
	if (f == -1) {
		close(pi[0]);
		close(pi[1]);
		return -1;
	}
	if (!f) {
		close(pi[0]);
		if (dup2(pi[1], 1) == -1) _exit(1);
		if (dup2(pi[1], 2) == -1) _exit(1);
		close(pi[1]);
		execlp("host", "host", name, NULL);
		execl("/usr/sbin/host", "host", name, NULL);
		_exit(1);
	}
	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);
	}
	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(name));
	}
	for (n = buffer; n < buffer + rd; n++) {
		if (*n >= '0' && *n <= '9') {
			if (get_addr_byte(&n, ((unsigned char *)host + 0), '.')) goto skip_addr;
			if (get_addr_byte(&n, ((unsigned char *)host + 1), '.')) goto skip_addr;
			if (get_addr_byte(&n, ((unsigned char *)host + 2), '.')) goto skip_addr;
			if (get_addr_byte(&n, ((unsigned char *)host + 3), 255)) goto skip_addr;
			return 0;
skip_addr:
			if (n >= buffer + rd) break;
		}
	}
	return -1;
}

#endif

int do_real_lookup(unsigned char *name, ip *host)
{
	unsigned char *n;
	struct hostent *hst;
	if (!*name) return -1;
	for (n = name; *n; n++) if (*n != '.' && (*n < '0' || *n > '9')) goto nogethostbyaddr;
	n = name;
	if (get_addr_byte(&n, ((unsigned char *)host + 0), '.')) goto skip_addr;
	if (get_addr_byte(&n, ((unsigned char *)host + 1), '.')) goto skip_addr;
	if (get_addr_byte(&n, ((unsigned char *)host + 2), '.')) goto skip_addr;
	if (get_addr_byte(&n, ((unsigned char *)host + 3), 0)) goto skip_addr;
	return 0;
	skip_addr:
#ifdef HAVE_GETHOSTBYADDR
	if (!(hst = gethostbyaddr(name, strlen(name), AF_INET)))
#endif
	{
		nogethostbyaddr:
#ifdef HAVE_GETHOSTBYNAME
		if (!(hst = gethostbyname(name)))
#endif
		{
#ifdef EXTERNAL_LOOKUP
			return do_external_lookup(name, host);
#endif
			return -1;
		}
	}
	memcpy(host, hst->h_addr_list[0], sizeof(ip));
	return 0;
}

void lookup_fn(unsigned char *name, int h)
{
	ip host;
	if (do_real_lookup(name, &host)) return;
	write(h, &host, sizeof(ip));
}

void end_real_lookup(struct dnsquery *q)
{
	int r = 1;
	if (!q->addr || read(q->h, q->addr, sizeof(ip)) != sizeof(ip)) goto end;
	r = 0;

	end:
	set_handlers(q->h, NULL, NULL, NULL, NULL);
	close(q->h);
	q->xfn(q, r);
}

void failed_real_lookup(struct dnsquery *q)
{
	set_handlers(q->h, NULL, NULL, NULL, NULL);
	close(q->h);
	q->xfn(q, -1);
}

int 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
		int r;
#ifndef NO_ASYNC_LOOKUP
		sync_lookup:
#endif
		r = do_real_lookup(q->name, q->addr);
		q->xfn(q, r);
		return 0;
#ifndef NO_ASYNC_LOOKUP
	} else {
		if ((q->h = start_thread((void (*)(void *, int))lookup_fn, q->name, strlen(q->name) + 1)) == -1) goto sync_lookup;
		set_handlers(q->h, (void (*)(void *))end_real_lookup, NULL, (void (*)(void *))failed_real_lookup, q);
		return 1;
	}
#endif
}

int do_queued_lookup(struct dnsquery *q)
{
#ifndef THREAD_SAFE_LOOKUP
	q->next_in_queue = NULL;
	if (!dns_queue) {
		dns_queue = q;
		/*debug("direct lookup");*/
#endif
		return do_lookup(q, 0);
#ifndef THREAD_SAFE_LOOKUP
	} else {
		/*debug("queuing lookup for %s", q->name);*/
		if (dns_queue->next_in_queue) internal("DNS queue corrupted");
		dns_queue->next_in_queue = q;
		dns_queue = q;
		return 1;
	}
#endif
}

int find_in_dns_cache(unsigned char *name, struct dnsentry **dnsentry)
{
	struct dnsentry *e;
	foreach(e, dns_cache)
		if (!strcasecmp(e->name, name)) {
			del_from_list(e);
			add_to_list(dns_cache, e);
			*dnsentry=e;
			return 0;
		}
	return -1;
}

void end_dns_lookup(struct dnsquery *q, int a)
{
	struct dnsentry *dnsentry;
	void (*fn)(void *, int);
	void *data;
	/*debug("end lookup %s", q->name);*/
#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(ip));
			a = 0;
			goto e;
		}
		del_from_list(dnsentry);
		mem_free(dnsentry);
	}
	if (a) goto e;
	dnsentry = mem_alloc(sizeof(struct dnsentry) + strlen(q->name) + 1);
	strcpy(dnsentry->name, q->name);
	memcpy(&dnsentry->addr, q->addr, sizeof(ip));
	dnsentry->get_time = get_time();
	add_to_list(dns_cache, dnsentry);
	e:
	if (q->s) *q->s = NULL;
	fn = q->fn;
	data = q->data;
	free(q);
	fn(data, a);
}

int find_host_no_cache(unsigned char *name, ip *addr, void **qp, void (*fn)(void *, int), void *data)
{
	struct dnsquery *q;
	if (!(q = (struct dnsquery *)malloc(sizeof(struct dnsquery) + strlen(name) + 1))) {
		fn(data, -1);
		return 0;
	}
	q->fn = fn;
	q->data = data;
	q->s = (struct dnsquery **)qp;
	q->addr = addr;
	strcpy(q->name, name);
	if (qp) *(struct dnsquery **) qp = q;
	q->xfn = end_dns_lookup;
	return do_queued_lookup(q);
}

int find_host(unsigned char *name, ip *addr, void **qp, void (*fn)(void *, int), void *data)
{
	struct dnsentry *dnsentry;
	if (qp) *qp = NULL;
	if (!find_in_dns_cache(name, &dnsentry)) {
		if ((uttime)get_time() - (uttime)dnsentry->get_time > DNS_TIMEOUT) goto timeout;
		memcpy(addr, &dnsentry->addr, sizeof(ip));
		fn(data, 0);
		return 0;
	}
	timeout:
	return find_host_no_cache(name, addr, qp, fn, data);
}

void kill_dns_request(void **qp)
{
	struct dnsquery *q = *qp;
	/*set_handlers(q->h, NULL, NULL, NULL, NULL);
	close(q->h);
	mem_free(q);*/
	q->fn = NULL;
	q->addr = NULL;
	*qp = NULL;
}

void shrink_dns_cache(int u)
{
	struct dnsentry *d, *e;
	foreach(d, dns_cache) if (u || (uttime)get_time() - (uttime)d->get_time > DNS_TIMEOUT) {
		e = d;
		d = d->prev;
		del_from_list(e);
		mem_free(e);
	}
}