
/*
 * Really Simple Threads package.  Copyright Robert S. Thau 1996.
 * This code may be used for any purpose.  It may be redistributed on
 * the following terms: 1) this paragraph and the next must appear
 * unaltered in any source distribution, and be prominently displayed
 * in the supporting documentation for any source or binary
 * distribution; 2) if you wish to distribute modified or derived
 * works, each such work must bear a notice of who last altered the
 * included version of Really Simple Threads, and when.  The notice of
 * alterations shall be attached to the derivative works in the same
 * manner as these two paragraphs, and shall be at least as prominent.
 * Redistribution for any purpose is otherwise unrestricted.
 *
 * THIS CODE IS IN AN UNFINISHED STATE, AND HAS NOT BEEN TESTED
 * TO THE POINT THAT THE AUTHOR BELIEVES IT CAN BE EXPECTED TO
 * WORK RELIABLY.  IT IS SUITABLE, AT THIS POINT, FOR EXPERIMENTATION
 * AND DEBUGGING ONLY.  TREAT ACCORDINGLY.
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <sys/time.h>
#ifndef ULTRIX            /* Protect your header files, why don't you? */
#include <sys/socket.h>
#endif
#include <memory.h>
#include <netdb.h>
#include <ctype.h>
#include <assert.h>
#include "rsthreads.h"
#include "rsthreads_dns.h"

#ifdef ULTRIX
#define nsaddr_list ns_list
#endif

#undef GETSHORT
#undef GETLONG

#define GETSHORT(ptr) ((ptr) += 2, ((ptr)[-2] << 8) + (ptr)[-1])
#define GETLONG(ptr) ((ptr) += 4, ((ptr)[-4] << 24) + ((ptr)[-3] << 16) \
                                  + ((ptr)[-2] << 8) + (ptr)[-1])

void *malloc();
void *calloc();
void free();

/* Managing communications with the name server.  We assume that all
 * queries will be fewer than 512 bytes in length, and that responses
 * which are longer will have enough of the answer left before truncation
 * to be useful.  The only way to handle queries and responses of
 * arbitrary size is TCP.  (So, if you've got 400-character hostnames,
 * you've got a problem with this code).
 *
 * The key data structure is the dns_qentry, which stores info on a
 * pending request to the name server.  The qry buffer contains the
 * request packet; ansbuf is to be filled with the response.  These
 * packets begin with a 16-bit transaction ID.  The requesting thread
 * puts a dns_qentry on a linked list, and then does an await_notify.
 *
 * This code runs a "daemon" thread which waits for replies from the name
 * server.  When the daemon gets one, it looks for the pending transaction 
 * with a matching ID.  Having found it, it fills the answer buffer, sets 
 * anssize to the number of bytes received, and notifies the thread
 * which is waiting for the transaction to finish.
 *
 * Got that?
 */
    
struct dns_qentry {
    struct dns_qentry *next;
    struct dns_qentry *prev;
    rst_cvar ready;
    char *qry;
    int qrysize;
    char *ansbuf;
    int ansbufsize;
    int anssize;
};

/*
 * Manipulating the linked list of dns_qnetries.  Note that dns_enqueue and
 * dns_dequeue should only be called with the dns_list_mutex held; likewise
 * for traversing the list.  The dirty secret here is that it doesn't
 * actually matter with a non-preemptive package like rsthreads; with a
 * preemptive one, though, you'd need the mutex.
 */

static rst_mutex dns_q_mutex = RST_MUTEX_INIT ("DNS");

static struct dns_qentry *dns_queue = NULL;

static void dns_enqueue (struct dns_qentry *q) {
    if (dns_queue == NULL) {
	q->next = q;
	q->prev = q;
	dns_queue = q;
    }
    else {
	q->next = dns_queue->next;
	q->prev = dns_queue;
	q->prev->next = q;
	q->next->prev = q;
    }
}

static void dns_dequeue (struct dns_qentry *q) {
    if (dns_queue == q && q == q->next && q == q->prev) {
	dns_queue = NULL;
    }
    else {
	q->prev->next = q->next;
	q->next->prev = q->prev;
	if (dns_queue == q) dns_queue = q->prev;
    }
}

/* Per-process variables --- the DNS socket used to send enquiries
 * to the server, and the pid that that socket is for.  The pid needs
 * to be recorded so that we can handle the case that someone fork()s
 * out from under us; in particular, we need to open a new socket, so
 * that our parent and all our children don't wind up reading each
 * others' DNS responses; the consequences of that are confusing and
 * generally useless.
 *
 * Something like pthread_atfork() might very well be a better way to
 * handle this...
 */

static int dns_socket = -1;    
static int dns_pid = -1;
static int have_dns_thread = 0;
    
static void reopen_dns_socket () {
    close (dns_socket);
    dns_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    rst_prepare_fd (dns_socket);
    dns_pid = getpid();
}

/* Receiving messages back from the name server.  Since we may have
 * multiple queries outstanding, we need to figure out which one this
 * is an answer to...
 */

static void *dns_thread (void *dummy) {
    char buf[512];
    int n;
    
    while (1) {
	rst_read_wait (RST_WAIT, dns_socket);

	if (dns_pid != getpid()) {
	    /* We forked.  Get a new socket; outstanding requests will have
	     * responses which were read by parent; copies of those threads
	     * in this address space will time out & retry
	     */
	    reopen_dns_socket();
	    continue;
	}
	
	n = recv (dns_socket, buf, sizeof(buf), 0);

	if (n > 2 && dns_queue != NULL) {
	    /* Have a response.  Figure out who wants it. */
	    unsigned short id = *(unsigned short *)buf;
	    struct dns_qentry *q;

	    rst_lock_mutex (RST_WAIT, &dns_q_mutex);
	    q = dns_queue;
	    
	    do {
		int q_id = *(unsigned short *)q->qry;

		if (id == q_id) {
		    q->anssize = n;
		    memcpy (q->ansbuf, buf,
			    q->ansbufsize < sizeof(buf)
			      ? q->ansbufsize : sizeof (buf));
		    rst_notify_one (&q->ready);
		}
		
		q = q->next;
	    } while (q != dns_queue);

	    rst_release_mutex (&dns_q_mutex);
	}
    }
}


/* Getting the above ball rolling */

static int dns_init () {
    rst_thread_parms dns_thread_params;
    
    if (!(_res.options & RES_INIT)) res_init();
    dns_socket = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (dns_socket < 0) return 0;

    dns_pid = getpid();		/* Used to cue reinitialization; see above */
    
    rst_prepare_fd (dns_socket);	

    dns_thread_params = rst_thread_defaults;
    dns_thread_params.flags |= RST_NO_JOIN;
#ifdef NOTDEF    
    /* 
     * dns_thread can actually live with a stack this small for its
     * own business, but if the user ever calls a function from an
     * inspection breakpoint, it'll overflow.  Stick with the default.
     */
    dns_thread_params.stacksize = 1;
#endif
    dns_thread_params.name = "DNS packet receiver";
    rst_spawn_parms (dns_thread, NULL, &dns_thread_params);
    return 1;
}

/*
 * The "top half" of the DNS engine --- called by a thread which wants
 * to query the server.  Set up a qentry, send off the first transmission,
 * and wait for the dns_thread to pick up a response.  The standard -lresolv
 * code uses a trick involving connect() to detect that the primary name
 * server is unavailable without a timeout (in some circumstances) ---
 * here, we don't bother, at least not yet.
 *
 * Also, start the dns_thread, and set up the socket if we haven't
 * done any of that yet.
 */

static unsigned short dns_qindex = 0;

static void dns_cleanup (void *qv) {
    rst_lock_mutex (RST_WAIT, &dns_q_mutex);
    dns_dequeue ((struct dns_qentry *)qv);
    rst_release_mutex (&dns_q_mutex);
    free (qv);
}

static int dns_send_query (char *qry, int qrysize, char *buf, int bufsize) {
    struct dns_qentry *q = malloc (sizeof (struct dns_qentry));
    int rv, try, server;

    if (dns_socket == -1 && !dns_init()) return -1;
    if (dns_pid != getpid()) reopen_dns_socket();
    
    q->qry = qry;
    q->qrysize = qrysize;
    q->ansbuf = buf;
    q->ansbufsize = bufsize;
    q->anssize = 0;
    rst_cvar_init (&q->ready, "DNS");

    *(unsigned short *)q->qry = dns_qindex++; /* Set ID */

    rst_lock_mutex (RST_WAIT, &dns_q_mutex);
    dns_enqueue (q);
    rst_cleanup_push (dns_cleanup, (void*)q);
    rst_release_mutex (&dns_q_mutex);
    
    for (try = 0; try < _res.retry; ++try)
	for (server= 0; server < _res.nscount; ++server)
	{
	    int resp_wait =	/* time to wait for response */
		(_res.retrans << try) / _res.nscount;
	    
	    if (resp_wait < 1) resp_wait = 1;
	    
	    rst_lock_mutex (RST_WAIT, &dns_q_mutex);
	    
	    while (sendto (dns_socket, q->qry, q->qrysize, 0,
			   &_res.nsaddr, sizeof(_res.nsaddr)) == -1)
		continue;

	    if (rst_await_notify (resp_wait, &dns_q_mutex, &q->ready) >=0)
		goto out;

	    /* If we timed out, rst_await_notify doesn't grab the
	     * mutex back, so we don't have to release it.
	     */
	    continue;
	}

    q->anssize = -1;		/* Timed out */

  out:
    if (rst_current_thread() != rst_mutex_holder (&dns_q_mutex))
	rst_lock_mutex (RST_WAIT, &dns_q_mutex);
    
    dns_dequeue (q);
    rst_cleanup_pop();
    rst_release_mutex (&dns_q_mutex);
    
    rv = q->anssize;
    free (q);
    return rv;
}

/*****************************************************************
 *
 * So much for low-level communications with the name server.
 * This next routine actually deals with the *structure* of the
 * name server queries and answers.  Fortunately, the resolver
 * code will build the query for us, but we have to take the
 * response apart by hand.
 *
 * This routine sends off a request for DNS records of the given
 * type and class, regarding the given hostname, and calls 'func'
 * for the first few found (up to max_rrec).  The function can
 * force an early return by itself returning nonzero.
 *
 * It returns the number of records processed, 0 if no information
 * was available (probably the target name was bad), and -1 if
 * something *really* screwy happened (internal error).
 */


static int dns_do_query (char *hostname, int type, int class, 
			 int (*func)(void *, struct rrec *, unsigned char *,
				     unsigned char *, unsigned char *),
			 void *cb_info)
{
    unsigned char buf[512], ans[1024];
    unsigned char *ansptr = ans + sizeof(HEADER);
    unsigned char *eom;
    HEADER *h = (HEADER *)&ans;
    int n, qdcount, anscount;
    int rrec_count = 0;
    struct rrec this_rrec;
    
    n = res_mkquery (QUERY, hostname, class, type, NULL, 0, NULL,
		     buf, sizeof(buf));

    if (n < 0) return -1;

    n = dns_send_query (buf, n, ans, sizeof(ans));
    eom = ans + n;

    if (n < 0) return -1;

    /* Got a response.  Sanity-check it... */

    if (!h->ra) return -1;	/* Not full-service name server??? */

    switch (h->rcode) {
    case NOERROR: break;	/* Success!  Success? */
    case NXDOMAIN: return 0;	/* No such domain */
    default: return -1;
    };

    /* Skip over query portion... */
    
    qdcount = ntohs (h->qdcount);
    anscount = ntohs (h->ancount);

    while (qdcount--) {
	n = dn_expand (ans, eom, ansptr, buf, sizeof(buf));
	if (n < 0) return -1;
	ansptr += n + 2 * sizeof(short);
    }

    /* Invoke callback func on matching RRs from answer portion... */

    while (anscount-- && ansptr < eom) {
	n = dn_expand (ans, eom, ansptr, buf, sizeof(buf)); /* NAME portion */
	if (n < 0) return rrec_count;
	ansptr += n;
	
	this_rrec.r_type = GETSHORT (ansptr);
	this_rrec.r_class = GETSHORT (ansptr);
	this_rrec.r_ttl = GETLONG (ansptr);
	this_rrec.r_size = GETSHORT (ansptr);
	this_rrec.r_data = ansptr;
	ansptr += this_rrec.r_size;

	if (ansptr > eom) return rrec_count;

	++rrec_count;
	if (func (cb_info, &this_rrec, ans, eom, buf)) break; 
    }

    return rrec_count;
}

/*****************************************************************
 *
 * Get host address(es), given the name.  Does a straight lookup, and 
 * grovels the response for RR's of type T_A (address).  Can return
 * multiple addresses (num_addr arg is the number that the caller is
 * prepared to accept).  Returns the number of addresses, 0, or -1
 * as above.
 *
 * Note that this routine accepts full hostnames only.  It does *not*
 * implement the usual conventions that at, say,
 *
 *   fine-ground.walnut.chopped-nuts.org
 *
 * a request for host 'coarse' should find coarse.walnut.chopped-nuts.org
 * or coarse.chopped-nuts.org if those exist.  (rst_gethostbyname does this;
 * see below).
 */


struct addr_query_info {
    struct in_addr *addrs;
    int num_addrs;
    unsigned long ttl;
};

static int do_addr_rrec (void *v, struct rrec *r,
			 unsigned char *msg, unsigned char *eom,
			 unsigned char *name)
{
    struct addr_query_info *q = (struct addr_query_info *)v;

    if (r->r_type != T_A) return 0;
    if (r->r_ttl < q->ttl) q->ttl = r->r_ttl; /* Take lowest time-to-live */
    memcpy ((char *)q->addrs, r->r_data, sizeof (struct in_addr));
    q->addrs++;
    return (--q->num_addrs > 0);
}

int rst_gethostaddrs (char *hostname, struct in_addr *addrs, int num_addrs,
		      unsigned long *time_to_live)
{
    struct addr_query_info q;
    int n;
    
    q.addrs = addrs;
    q.num_addrs = num_addrs;
    q.ttl = (unsigned long)-1;	/* integer infinity */
    
    n = dns_do_query (hostname, T_A, C_IN, do_addr_rrec, (void*)&q);

    if (time_to_live) *time_to_live = q.ttl;
    return n;
}

/****************************************************************
 *
 * Host name from address.  Does a search for T_PTR records in the
 * black-magic in-addr.arpa domain.  Returns 1 on success, 0 if it
 * couldn't find anything, and -1 if things *really* went south.
 */

struct name_query_info {
    char *buf;
    int buflen;
    unsigned long ttl;
    int err;
};

static int do_name_rrec (void *v, struct rrec *r,
			 unsigned char *msg, unsigned char *eom,
			 unsigned char *name)
{
    struct name_query_info *q = (struct name_query_info *)v;
    
    if (r->r_type != T_PTR) return 0;
    q->err = dn_expand (msg, eom, r->r_data, q->buf, q->buflen);
    q->ttl = r->r_ttl;
    return 0;
}

int rst_gethostname (struct in_addr *addr, char *namebuf, int buflen,
		     unsigned long *time_to_live)
{
    struct name_query_info q;
    unsigned char *addrmem = (unsigned char *)addr;
    char address[80];
    int n;

    q.buf = namebuf;
    q.buflen = buflen;
    sprintf(address, "%u.%u.%u.%u.in-addr.arpa",
	    addrmem[3], addrmem[2], addrmem[1], addrmem[0]);

    n = dns_do_query (address, T_PTR, C_IN, do_name_rrec, (void*)&q);

    if (time_to_live) *time_to_live = q.ttl;
    return n;
}

/****************************************************************
 *
 * Gethostbyfoo() functions --- ick.
 */

/* Per-thread keys for gethostbyfoo() */

#if defined(h_addr)
#define MIN_ALLOC_ADDRS 8	/* Probably adequate ... */
#else
#define MIN_ALLOC_ADDRS 1	/* Can't give more if we want to. */
#endif

#define MIN_ALIASES 3
#define MIN_ALLOC_NAME_LEN 100

static int inited_gethost = 0;

static rst_per_thread_key hostent_per_thread;
static rst_per_thread_key addrs_per_thread;
static rst_per_thread_key addr_ptrs_per_thread;
static rst_per_thread_key hname_per_thread;
static rst_per_thread_key aliases_per_thread;

typedef struct {
    struct hostent htent;
    struct in_addr *addrs;
    int num_addrs_yet;
    int num_addrs_alloc;
    char *first_addr_ptrs[MIN_ALLOC_ADDRS];
    char *first_alias_ptrs[MIN_ALIASES + 1];
    struct in_addr first_addrs[MIN_ALLOC_ADDRS];
    char first_hname[MIN_ALLOC_NAME_LEN];
    char first_aliases[MIN_ALLOC_NAME_LEN * MIN_ALIASES];
} gethost_data;

static gethost_data *setup_gethost() {
    gethost_data *dat;

    if (!inited_gethost) {
	hostent_per_thread = rst_make_per_thread (free);
	addrs_per_thread = rst_make_per_thread (free);
	addr_ptrs_per_thread = rst_make_per_thread (free);
	hname_per_thread = rst_make_per_thread (free);
	inited_gethost = 1;
    }
    
    dat = (gethost_data *)rst_get_per_thread(hostent_per_thread);
	
    if (dat == NULL) {
	rst_set_per_thread (hostent_per_thread,
			    malloc (sizeof (gethost_data)));
	dat = (gethost_data *)rst_get_per_thread(hostent_per_thread);
	
	/* Start out pointing to preallocated addrs block */ 
    
	dat->num_addrs_alloc = MIN_ALLOC_ADDRS;
	dat->addrs = dat->first_addrs;
#if defined (h_addr)
	dat->htent.h_addr_list = dat->first_addr_ptrs;
#endif
    }

    if (dat == NULL) assert (!"malloc failure in setup_gethost");

    /* Make sure hostent never points at trash  */

    dat->htent.h_name = dat->first_hname;
    dat->htent.h_aliases = dat->first_alias_ptrs;
    dat->htent.h_addrtype = AF_INET;
    dat->htent.h_length = sizeof (struct in_addr);
    dat->first_alias_ptrs[0] = NULL;

    /* No info yet */
    
    dat->num_addrs_yet = 0;
    dat->htent.h_name[0] = '\0';
    
    return dat;
}

static void assure_hostname_space (gethost_data *dat, int len)
{
    if (len < MIN_ALLOC_NAME_LEN) {
	dat->htent.h_name = dat->first_hname;
    }
    else {
	char *cp = rst_get_per_thread (hname_per_thread);
	char *newp = malloc (len);

	if (cp) { free (cp); }	/* might not be long enough... */
	if (!newp) assert (!"malloc failure in assure_hostname_space");
	rst_set_per_thread (hname_per_thread, newp);
	dat->htent.h_name = newp;
    }
}

static void add_hostname (gethost_data *dat, char *hname)
{
    assure_hostname_space (dat, strlen (hname) + 1);
    strcpy (dat->htent.h_name, hname);
}

static void add_hostaddr (gethost_data *dat, struct in_addr *addr)
{
#if !(defined (h_addr))
    dat->num_addrs_yet = 1;
    dat->addrs[0] = *addr;	/* Only room for one; why sweat? */
#else    
    if (dat->num_addrs_yet >= dat->num_addrs_alloc) {
	/* Handle overflow */
	int new_num_alloc = 2 * dat->num_addrs_alloc;
	char **new_addr_ptrs = calloc (new_num_alloc, sizeof (char *));
	struct in_addr *new_addrs =
	    calloc (new_num_alloc, sizeof (struct in_addr));

	if (new_addr_ptrs == NULL || new_addrs == NULL)
	    assert (!"malloc failure in add_hostaddr (for DNS lookup)");

	memcpy ((char *)new_addrs, (char *)dat->addrs,
		sizeof (struct in_addr) * dat->num_addrs_alloc);
	
	if (dat->num_addrs_alloc > MIN_ALLOC_ADDRS) {
	    free (dat->htent.h_addr_list);
	    free (dat->addrs);
	}

	dat->htent.h_addr_list = new_addr_ptrs;
	dat->addrs = new_addrs;
	dat->num_addrs_alloc = new_num_alloc;

	rst_set_per_thread (addrs_per_thread, (void *)new_addrs);
	rst_set_per_thread (addr_ptrs_per_thread, (void *)new_addr_ptrs);
    }

    memcpy ((char *)&dat->addrs [dat->num_addrs_yet++], (char *)addr,
	    sizeof (struct in_addr));
#endif    
}

/* Aliases.  No overflow handling for these... is it worth the pain?
 * In the typical case, there won't be more than one, that being the
 * *particular* hostname that we looked up, if it was a CNAME.
 *
 * (FWIW, as of 4.9.2, the BIND authors didn't think so...).
 */

static void add_alias (gethost_data *dat, char *alias) {
    int i;

    /* nextp always points one beyond the last alias name byte known in use */
    
    char *nextp = dat->first_aliases;

    for (i = 0; i <= MIN_ALIASES; ++i) {
	char *ap = dat->first_alias_ptrs[i];
	if (ap != NULL)
	    nextp = ap + strlen (ap) + 1;
	else {
	    /* Found free slot... data goes at nextp, if we have room */
	    char *endp = dat->first_aliases + sizeof (dat->first_aliases);
	    
	    if (strlen (alias) < endp - nextp) {
		dat->first_alias_ptrs[i] = nextp;
		dat->first_alias_ptrs[i + 1] = NULL;
		strcpy (dat->first_alias_ptrs[i], alias);
	    }
	    break;
	}
    }
}

static struct hostent *gethost_wrapup (gethost_data *dat) {
#if !defined (h_addr)
    dat->htent.h_addr = (char *)dat->addrs;
#else    
    int i;
    struct in_addr dummy;

    add_hostaddr (dat, &dummy);
    
    for (i = 0; i < dat->num_addrs_yet - 1; ++i)
	dat->htent.h_addr_list[i] = (char *)&dat->addrs[i];

    dat->htent.h_addr_list [dat->num_addrs_yet - 1] = NULL;
#endif
    return &dat->htent;
}

static int do_gethost_rrec (void *v, struct rrec *r,
			    unsigned char *msg, unsigned char *eom,
			    unsigned char *name)
{
    gethost_data *dat = (gethost_data *)v;
    char buf[512];

    switch (r->r_type) {
    case T_PTR:
	dn_expand (msg, eom, r->r_data, buf, sizeof(buf));
	add_hostname (dat, buf);
	return 1;
    case T_A:
	if (r->r_class == C_IN) {
	    add_hostname (dat, name);
	    add_hostaddr (dat, (struct in_addr *)r->r_data);
	}
	return 0;
    case T_CNAME:
	add_alias (dat, name);
	return 0;
    default:
	return 0;
    }
}

struct hostent *rst_gethostbyaddr(char *addr, int addrlen, int addr_fmt)
{
    gethost_data *dat = setup_gethost();
    unsigned char *addrmem = (unsigned char*)addr;
    char address[80];
    
    if (addr_fmt != AF_INET || addrlen != sizeof (struct in_addr))
	return NULL;

    sprintf (address, "%u.%u.%u.%u.in-addr.arpa",
	     addrmem[3], addrmem[2], addrmem[1], addrmem[0]);

    if (dns_do_query (address, T_PTR, C_IN, do_gethost_rrec, (void*)dat) > 0) {
	add_hostaddr (dat, (struct in_addr *)addr);
	return gethost_wrapup (dat);
    }

    return NULL;
}

struct hostent *rst_gethostbyname(char *hostname)
{
    gethost_data *dat = setup_gethost();
    int has_trailing_dot = 0;
    int ndots = 0;
    int seen_non_digit = 0;
    char buf[130];
    char *cp;
    int i;

    if (dns_socket == -1 && !dns_init()) return NULL;
    
    /* Figure out grammar */

    for (cp = hostname; *cp; ++cp)
	if (*cp == '.')
	    ++ndots;
	else if (!isdigit (*cp))
	    seen_non_digit = 1;

    if (cp > hostname && cp[-1] == '.')
	has_trailing_dot = 1;

    /* Fake up hostent for raw numeric addr... */

    if (!seen_non_digit) {
	int c0, c1, c2, c3;
	
	if (ndots != 3) return NULL;
	add_hostname (dat, hostname);
	sscanf (hostname, "%d.%d.%d.%d", &c0, &c1, &c2, &c3);
	buf[0] = c0; buf[1] = c1; buf[2] = c2; buf[3] = c3;
	add_hostaddr (dat, (struct in_addr *)&buf);
	return gethost_wrapup (dat);
    }

    /* Real hostname?  If has dots, first try as is ...
     * (NB we only return success if we see an address --- we don't need
     * any steenking MX-only hosts...).
     */

    if (ndots > 0) {
	if (dns_do_query(hostname, T_A, C_IN, do_gethost_rrec, (void*)dat)>0
	    && dat->num_addrs_yet > 0)
	    return gethost_wrapup (dat);
    }

    /* That didn't work.  If not absolute DNS name, try subdomains */

    for (i = 0; _res.dnsrch[i]; ++i) {
	sprintf (buf, "%.60s.%.60s", hostname, _res.dnsrch[i]);
	if (dns_do_query (buf, T_ANY, C_IN, do_gethost_rrec, (void*)dat) > 0
	    && dat->num_addrs_yet > 0)
	    return gethost_wrapup (dat);
    }

    /* Now, try default domain */

    sprintf (buf, "%.60s.%.60s", hostname, _res.defdname);
    if (dns_do_query (buf, T_ANY, C_IN, do_gethost_rrec, (void*)dat) > 0
	&& dat->num_addrs_yet > 0)
	return gethost_wrapup (dat);

    /* Okay, I give up... */

    return NULL;
}

#ifdef SUNOS
/* Some machines need strerror... sigh... */

char *strerror (int errnum) {
    extern int sys_nerr;
    extern char* sys_errlist[];
    
    if (errnum < 0 || errnum >= sys_nerr) return "Unknown error????";
    else return sys_errlist[errnum];
}
#endif

#ifdef __GCC__
static typeof(_res) *gubbish = &_res; /* So I can look at it with gdb... */
#endif
