/*
 * Copyright (c) 1997, 1998, 1999, 2002, 2003
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
#ifndef lint
static const char rcsid[] =
    "@(#) $Id: nb_dns.c,v 1.10 2003/03/26 03:47:40 leres Exp $ (LBL)";
#endif
/*
 * nb_dns - non-blocking dns routines
 *
 * This version works with BIND 9
 *
 * Note: The code here is way more complicated than it should be but
 * although the interface to send requests is public, the routine to
 * crack reply buffers is private.
 */

#include "config.h"			/* must appear before first ifdef */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>
#include <arpa/nameser.h>

#include <errno.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <netdb.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef notdef
#include "gnuc.h"
#ifdef HAVE_OS_PROTO_H
#include "os-proto.h"
#endif
#endif

#include "nb_dns.h"

#if PACKETSZ > 1024
#define MAXPACKET	PACKETSZ
#else
#define MAXPACKET	1024
#endif

#ifdef DO_SOCK_DECL
extern int socket(int, int, int);
extern int connect(int, const struct sockaddr *, int);
extern int send(int, const void *, int, int);
extern int recvfrom(int, void *, int, int, struct sockaddr *, int *);
#endif

/* Private data */
struct nb_dns_entry {
	struct nb_dns_entry *next;
	char name[NS_MAXDNAME + 1];
	int qtype;			/* query type */
	int atype;			/* address family */
	int asize;			/* address size */
	u_short id;
	void *cookie;
};

#ifndef MAXALIASES
#define MAXALIASES	35
#endif
#ifndef MAXADDRS
#define MAXADDRS	35
#endif

struct nb_dns_hostent {
	struct hostent hostent;
	int numaliases;
	int numaddrs;
	char *host_aliases[MAXALIASES + 1];
	char *h_addr_ptrs[MAXADDRS + 1];
	char hostbuf[8 * 1024];
};

struct nb_dns_info {
	int s;				/* Resolver file descriptor */
	struct sockaddr_in server;	/* server address to bind to */
	struct nb_dns_entry *list;	/* outstanding requests */
	struct nb_dns_hostent dns_hostent;
};

/* Forwards */
static int _nb_dns_mkquery(struct nb_dns_info *, const char *, int, int,
    void *, char *);
static int _nb_dns_cmpsockaddr(struct sockaddr *, struct sockaddr *, char *);

static char *
my_strerror(int errnum)
{
#if HAVE_STRERROR
	extern char *strerror(int);
	return strerror(errnum);
#else
	static char errnum_buf[32];
	snprintf(errnum_buf, sizeof(errnum_buf), "errno %d", errnum);
	return errnum_buf;
#endif
}

struct nb_dns_info *
nb_dns_init(char *errstr)
{
	register struct nb_dns_info *nd;

	nd = (struct nb_dns_info *)malloc(sizeof(*nd));
	if (nd == NULL) {
		snprintf(errstr, NB_DNS_ERRSIZE, "nb_dns_init: malloc(): %s",
		    my_strerror(errno));
		return (NULL);
	}
	memset(nd, 0, sizeof(*nd));
	nd->s = -1;

	/* XXX should be able to init static hostent struct some other way */
	(void)gethostbyname("localhost.");

	if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
		snprintf(errstr, NB_DNS_ERRSIZE, "res_init() failed");
		free(nd);
		return (NULL);
	}
	nd->s = socket(PF_INET, SOCK_DGRAM, 0);
	if (nd->s < 0) {
		snprintf(errstr, NB_DNS_ERRSIZE, "socket(): %s",
		    my_strerror(errno));
		free(nd);
		return (NULL);
	}

	/* XXX should use resolver config */
	nd->server = _res.nsaddr_list[0];

	if (connect(nd->s, (struct sockaddr *)&nd->server,
	    sizeof(struct sockaddr)) < 0) {
		snprintf(errstr, NB_DNS_ERRSIZE, "connect(%s): %s",
		    inet_ntoa(nd->server.sin_addr), my_strerror(errno));
		close(nd->s);
		free(nd);
		return (NULL);
	}

	return (nd);
}

void
nb_dns_finish(struct nb_dns_info *nd)
{
	register struct nb_dns_entry *ne, *ne2;

	ne = nd->list;
	while (ne != NULL) {
		ne2 = ne;
		ne = ne->next;
		free(ne2);
	}
	close(nd->s);
	free(nd);
}

int
nb_dns_fd(struct nb_dns_info *nd)
{

	return (nd->s);
}

static int
_nb_dns_cmpsockaddr(register struct sockaddr *sa1,
    register struct sockaddr *sa2, register char *errstr)
{
	register struct sockaddr_in *sin1, *sin2;
#ifdef AF_INET6
	register struct sockaddr_in6 *sin6a, *sin6b;
#endif
	const static char serr[] = "answer from wrong nameserver (%d)";

	if (sa1->sa_family != sa1->sa_family) {
		snprintf(errstr, NB_DNS_ERRSIZE, serr, 1);
		return (-1);
	}
	switch (sa1->sa_family) {

	case AF_INET:
		sin1 = (struct sockaddr_in *)sa1;
		sin2 = (struct sockaddr_in *)sa2;
		if (sin1->sin_port != sin2->sin_port) {
			snprintf(errstr, NB_DNS_ERRSIZE, serr, 2);
			return (-1);
		}
		if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr) {
			snprintf(errstr, NB_DNS_ERRSIZE, serr, 3);
			return (-1);
		}
		break;

#ifdef AF_INET6
	case AF_INET6:
		sin6a = (struct sockaddr_in6 *)sa1;
		sin6b = (struct sockaddr_in6 *)sa2;
		if (sin6a->sin6_port != sin6b->sin6_port) {
			snprintf(errstr, NB_DNS_ERRSIZE, serr, 2);
			return (-1);
		}
		if (memcmp(&sin6a->sin6_addr, &sin6b->sin6_addr,
		    sizeof(sin6a->sin6_addr)) != 0) {
			snprintf(errstr, NB_DNS_ERRSIZE, serr, 3);
			return (-1);
		}
		break;
#endif

	default:
		snprintf(errstr, NB_DNS_ERRSIZE, serr, 4);
		return (-1);
	}
	return (0);
}

static int
_nb_dns_mkquery(register struct nb_dns_info *nd, register const char *name,
    register int atype, register int qtype, register void * cookie,
    register char *errstr)
{
	register struct nb_dns_entry *ne;
	register HEADER *hp;
	register int n;
	u_long msg[MAXPACKET / sizeof(u_long)];

	/* Allocate an entry */
	ne = (struct nb_dns_entry *)malloc(sizeof(*ne));
	if (ne == NULL) {
		snprintf(errstr, NB_DNS_ERRSIZE, "malloc(): %s",
		    my_strerror(errno));
		return (-1);
	}
	memset(ne, 0, sizeof(*ne));
	strncpy(ne->name, name, sizeof(ne->name));
	ne->name[sizeof(ne->name) - 1] = '\0';
	ne->qtype = qtype;
	ne->atype = atype;
	switch (atype) {

	case AF_INET:
		ne->asize = NS_INADDRSZ;
		break;

#ifdef AF_INET6
	case AF_INET6:
		ne->asize = NS_IN6ADDRSZ;
		break;
#endif

	default:
		snprintf(errstr, NB_DNS_ERRSIZE,
		    "_nb_dns_mkquery: bad family %d", atype);
		return (-1);
	}

	/* Build the request */
	n = res_mkquery(
	    ns_o_query,			/* op code (query) */
	    name,			/* domain name */
	    ns_c_in,			/* query class (internet) */
	    qtype,			/* query type */
	    NULL,			/* data */
	    0,				/* length of data */
	    NULL,			/* new rr */
	    (char *)msg,		/* buffer */
	    sizeof(msg));		/* size of buffer */
	if (n < 0) {
		snprintf(errstr, NB_DNS_ERRSIZE, "res_mkquery() failed");
		free(ne);
		return (-1);
	}

	hp = (HEADER *)msg;
	ne->id = htons(hp->id);

	if (send(nd->s, (char *)msg, n, 0) != n) {
		snprintf(errstr, NB_DNS_ERRSIZE, "send(): %s",
		    my_strerror(errno));
		free(ne);
		return (-1);
	}

	ne->next = nd->list;
	ne->cookie = cookie;
	nd->list = ne;

	return(0);
}

int
nb_dns_host_request(register struct nb_dns_info *nd, register const char *name,
    register void *cookie, register char *errstr)
{

	return (nb_dns_host_request2(nd, name, AF_INET, cookie, errstr));
}

int
nb_dns_host_request2(register struct nb_dns_info *nd, register const char *name,
    register int af, register void *cookie, register char *errstr)
{
	register int qtype;

	switch (af) {

	case AF_INET:
		qtype = T_A;
		break;

#ifdef AF_INET6
	case AF_INET6:
		qtype = T_AAAA;
		break;
#endif

	default:
		snprintf(errstr, NB_DNS_ERRSIZE,
		    "nb_dns_host_request2(): uknown address family %d", af);
		return (-1);
	}
	return (_nb_dns_mkquery(nd, name, af, qtype, cookie, errstr));
}

int
nb_dns_addr_request(register struct nb_dns_info *nd, nb_uint32_t addr,
    register void *cookie, register char *errstr)
{

	return (nb_dns_addr_request2(nd, (char *)&addr, AF_INET,
	    cookie, errstr));
}

int
nb_dns_addr_request2(register struct nb_dns_info *nd, char *addrp,
    register int af, register void *cookie, register char *errstr)
{
#ifdef AF_INET6
	register char *cp;
	register int n, i;
	register size_t size;
#endif
	register u_char *uaddr;
	char name[NS_MAXDNAME + 1];

	switch (af) {

	case AF_INET:
		uaddr = (u_char *)addrp;
		snprintf(name, sizeof(name), "%u.%u.%u.%u.in-addr.arpa",
		    (uaddr[3] & 0xff),
		    (uaddr[2] & 0xff),
		    (uaddr[1] & 0xff),
		    (uaddr[0] & 0xff));
		break;

#ifdef AF_INET6
	case AF_INET6:
		uaddr = (u_char *)addrp;
		cp = name;
		size = sizeof(name);
		for (n = NS_IN6ADDRSZ - 1; n >= 0; --n) {
			snprintf(cp, size, "%x.%x.",
			    (uaddr[n] & 0xf),
			    (uaddr[n] >> 4) & 0xf);
			i = strlen(cp);
			size -= i;
			cp += i;
		}
		snprintf(cp, size, "ip6.int");
		break;
#endif

	default:
		snprintf(errstr, NB_DNS_ERRSIZE,
		    "nb_dns_addr_request2(): uknown address family %d", af);
		return (-1);
	}

	return (_nb_dns_mkquery(nd, name, af, T_PTR, cookie, errstr));
}

int
nb_dns_abort_request(struct nb_dns_info *nd, void *cookie)
{
	register struct nb_dns_entry *ne, *lastne;

	/* Try to find this request on the outstanding request list */
	lastne = NULL;
	for (ne = nd->list; ne != NULL; ne = ne->next) {
		if (ne->cookie == cookie)
			break;
		lastne = ne;
	}

	/* Not a currently pending request */
	if (ne == NULL)
		return (-1);

	/* Unlink this entry */
	if (lastne == NULL)
		nd->list = ne->next;
	else
		lastne->next = ne->next;
	ne->next = NULL;

	return (0);
}

/* Returns 1 with an answer, 0 when reply was old, -1 on fatal errors */
int
nb_dns_activity(struct nb_dns_info *nd, struct nb_dns_result *nr, char *errstr)
{
	register int msglen, qtype, atype, n, i;
	register struct nb_dns_entry *ne, *lastne;
	int fromlen;
	struct sockaddr from;
	u_long msg[MAXPACKET / sizeof(u_long)];
	register u_char *bp, *ep;
	register char **ap, **hap;
	register u_int16_t id;
	register const u_char *rdata;
	register struct hostent *he;
	register size_t rdlen;
	ns_msg handle;
	ns_rr rr;

	/* This comes from the second half of do_query() */
	fromlen = sizeof(from);
	msglen = recvfrom(nd->s, (char *)msg, sizeof(msg), 0, &from, &fromlen);
	if (msglen <= 0) {
		snprintf(errstr, NB_DNS_ERRSIZE, "recvfrom(): %s",
		    my_strerror(errno));
		return (-1);
	}
	if (msglen < HFIXEDSZ) {
		snprintf(errstr, NB_DNS_ERRSIZE, "recvfrom(): undersized: %d",
		    msglen);
		return (-1);
	}
	if (ns_initparse((u_char *)msg, msglen, &handle) < 0) {
		snprintf(errstr, NB_DNS_ERRSIZE, "ns_initparse(): %s",
		    my_strerror(errno));
		nr->host_errno = NO_RECOVERY;
		return (-1);
	}

	/* RES_INSECURE1 style check */
	if (_nb_dns_cmpsockaddr((struct sockaddr *)&nd->server, &from,
	    errstr) < 0) {
		nr->host_errno = NO_RECOVERY;
		return (-1);
	}

	/* Search for this request */
	lastne = NULL;
	id = ns_msg_id(handle);
	for (ne = nd->list; ne != NULL; ne = ne->next) {
		if (ne->id == id)
			break;
		lastne = ne;
	}

	/* Not an answer to a question we care about anymore */
	if (ne == NULL)
		return (0);

	/* Unlink this entry */
	if (lastne == NULL)
		nd->list = ne->next;
	else
		lastne->next = ne->next;
	ne->next = NULL;

	/* RES_INSECURE2 style check */
	/* XXX not implemented */

	/* Initialize result struct */
	memset(nr, 0, sizeof(*nr));
	nr->cookie = ne->cookie;
	qtype = ne->qtype;

	/* Deal with various errors */
	switch (ns_msg_getflag(handle, ns_f_rcode)) {

	case ns_r_nxdomain:
		nr->host_errno = HOST_NOT_FOUND;
		free(ne);
		return (1);

	case ns_r_servfail:
		nr->host_errno = TRY_AGAIN;
		free(ne);
		return (1);

	case ns_r_noerror:
		break;

	case ns_r_formerr:
	case ns_r_notimpl:
	case ns_r_refused:
	default:
		nr->host_errno = NO_RECOVERY;
		free(ne);
		return (1);
	}

	/* Loop through records in packet */
	memset(&rr, 0, sizeof(rr));
	memset(&nd->dns_hostent, 0, sizeof(nd->dns_hostent));
	he = &nd->dns_hostent.hostent;
	/* XXX no support for aliases */
	he->h_aliases = nd->dns_hostent.host_aliases;
	he->h_addr_list = nd->dns_hostent.h_addr_ptrs;
	he->h_addrtype = ne->atype;
	he->h_length = ne->asize;
	free(ne);

	bp = nd->dns_hostent.hostbuf;
	ep = bp + sizeof(nd->dns_hostent.hostbuf);
	hap = he->h_addr_list;
	ap = he->h_aliases;

	for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) {
		/* Parse next record */
		if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
			if (errno != ENODEV) {
				nr->host_errno = NO_RECOVERY;
				return (1);
			}
			/* All done */
			break;
		}

		/* Ignore records that don't answer our query (e.g. CNAMEs) */
		atype = ns_rr_type(rr);
		if (atype != qtype)
			continue;

		rdata = ns_rr_rdata(rr);
		rdlen = ns_rr_rdlen(rr);
		switch (atype) {

		case T_A:
		case T_AAAA:
			if (rdlen != he->h_length) {
				snprintf(errstr, NB_DNS_ERRSIZE,
				    "nb_dns_activity(): bad rdlen %d", rdlen);
				nr->host_errno = NO_RECOVERY;
				return (-1);
			}

			if (bp + rdlen >= ep) {
				snprintf(errstr, NB_DNS_ERRSIZE,
				    "nb_dns_activity(): overflow 1");
				nr->host_errno = NO_RECOVERY;
				return (-1);
			}
			if (nd->dns_hostent.numaddrs + 1 >= MAXADDRS) {
				snprintf(errstr, NB_DNS_ERRSIZE,
				    "nb_dns_activity(): overflow 2");
				nr->host_errno = NO_RECOVERY;
				return (-1);
			}
			memcpy(bp, rdata, rdlen);
			*hap++ = bp;
			bp += rdlen;
			++nd->dns_hostent.numaddrs;

			/* Keep looking for more A records */
			break;

		case T_PTR:
			n = dn_expand((const u_char *)msg,
			    (const u_char *)msg + msglen, rdata, bp, ep - bp);
			if (n < 0) {
				/* XXX return -1 here ??? */
				nr->host_errno = NO_RECOVERY;
				return (1);
			}
			he->h_name = bp;
			/* XXX check for overflow */
			bp += n;		/* returned len includes EOS */

			/* "Find first satisfactory answer" */
			nr->hostent = he;
			return (1);
		}
	}

	nr->hostent = he;
	return (1);
}
