/*
 * Copyright (c) 1985, 1988, 1993
 *    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 the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. 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 BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Silicon Graphics, Inc. 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "stx.h"

#define MAXPACKET 1024

#if !defined(NETDB_INTERNAL) && defined(h_NETDB_INTERNAL)
#define NETDB_INTERNAL h_NETDB_INTERNAL
#endif

/* New in Solaris 7 */
#if !defined(_getshort) && defined(ns_get16)
#define _getshort(cp) ns_get16(cp)
#define _getlong(cp)  ns_get32(cp)
#endif

typedef union {
    HEADER hdr;
    u_char buf[MAXPACKET];
} querybuf_t;

int _stx_dns_ttl;


static int parse_answer(querybuf_t *ans, int len, struct in_addr *addrs,
			int *num_addrs)
{
    char buf[MAXPACKET];
    HEADER *ahp;
    u_char *cp, *eoa;
    int type, n, i;

    ahp = &ans->hdr;
    eoa = ans->buf + len;
    cp = ans->buf + sizeof(HEADER);
    h_errno = TRY_AGAIN;
    _stx_dns_ttl = -1;
    i = 0;

    while (ahp->qdcount > 0) {
	ahp->qdcount--;
	cp += dn_skipname(cp, eoa) + QFIXEDSZ;
    }
    while (ahp->ancount > 0 && cp < eoa && i < *num_addrs) {
	ahp->ancount--;
	if ((n = dn_expand(ans->buf, eoa, cp, buf, sizeof(buf))) < 0)
	    return -1;
	cp += n;
	if (cp + 4 + 4 + 2 >= eoa)
	    return -1;
	type = _getshort(cp);
	cp += 4;
	if (type == T_A)
	    _stx_dns_ttl = _getlong(cp);
	cp += 4;
	n = _getshort(cp);
	cp += 2;
	if (type == T_A) {
	    if (n > sizeof(*addrs) || cp + n > eoa)
		return -1;
	    memcpy(&addrs[i++], cp, n);
	}
	cp += n;
    }

    *num_addrs = i;
    return 0;
}


static int query_domain(st_netfd_t nfd, const char *name,
			struct in_addr *addrs, int *num_addrs,
			st_utime_t timeout)
{
    querybuf_t qbuf;
    u_char *buf = qbuf.buf;
    HEADER *hp = &qbuf.hdr;
    int blen = sizeof(qbuf);
    int i, len, id;

    for (i = 0; i < _res.nscount; i++) {
	len = res_mkquery(QUERY, name, C_IN, T_A, NULL, 0, NULL, buf, blen);
	if (len <= 0) {
	    h_errno = NO_RECOVERY;
	    return -1;
	}
	id = hp->id;

	if (st_sendto(nfd, buf, len, (struct sockaddr *)&(_res.nsaddr_list[i]),
		      sizeof(struct sockaddr), timeout) != len) {
	    h_errno = NETDB_INTERNAL;
	    /* EINTR means interrupt by other thread, NOT by a caught signal */
	    if (errno == EINTR)
		return -1;
	    continue;
	}

	/* Wait for reply */
	do {
	    len = st_recvfrom(nfd, buf, blen, NULL, NULL, timeout);
	    if (len <= 0)
		break;
	} while (id != hp->id);

	if (len < HFIXEDSZ) {
	    h_errno = NETDB_INTERNAL;
	    if (len >= 0)
		errno = EMSGSIZE;
	    else if (errno == EINTR)  /* see the comment above */
		return -1;
	    continue;
	}

	hp->ancount = ntohs(hp->ancount);
	hp->qdcount = ntohs(hp->qdcount);
	if ((hp->rcode != NOERROR) || (hp->ancount == 0)) {
	    switch (hp->rcode) {
	    case NXDOMAIN:
		h_errno = HOST_NOT_FOUND;
		break;
	    case SERVFAIL:
		h_errno = TRY_AGAIN;
		break;
	    case NOERROR:
		h_errno = NO_DATA;
		break;
	    case FORMERR:
	    case NOTIMP:
	    case REFUSED:
	    default:
		h_errno = NO_RECOVERY;
	    }
	    continue;
	}

	if (parse_answer(&qbuf, len, addrs, num_addrs) == 0)
	    return 0;
    }

    return -1;
}


#define CLOSE_AND_RETURN(ret) \
  {                           \
    n = errno;                \
    st_netfd_close(nfd);      \
    errno = n;                \
    return (ret);             \
  }


int _stx_dns_getaddrlist(const char *host, struct in_addr *addrs,
                         int *num_addrs, st_utime_t timeout)
{
    char name[MAXDNAME], **domain;
    const char *cp;
    int s, n, maxlen, dots;
    int trailing_dot, tried_as_is;
    st_netfd_t nfd;

    if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
	h_errno = NETDB_INTERNAL;
	return -1;
    }
    if (_res.options & RES_USEVC) {
	h_errno = NETDB_INTERNAL;
	errno = ENOSYS;
	return -1;
    }
    if (!host || *host == '\0') {
	h_errno = HOST_NOT_FOUND;
	return -1;
    }

    /* Create UDP socket */
    if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
	h_errno = NETDB_INTERNAL;
	return -1;
    }
    if ((nfd = st_netfd_open_socket(s)) == NULL) {
	h_errno = NETDB_INTERNAL;
	n = errno;
	close(s);
	errno = n;
	return -1;
    }

    maxlen = sizeof(name) - 1;
    n = 0;
    dots = 0;
    trailing_dot = 0;
    tried_as_is = 0;

    for (cp = host; *cp && n < maxlen; cp++) {
	dots += (*cp == '.');
	name[n++] = *cp;
    }
    if (name[n - 1] == '.')
	trailing_dot = 1;

    /*
     * If there are dots in the name already, let's just give it a try
     * 'as is'.  The threshold can be set with the "ndots" option.
     */
    if (dots >= _res.ndots) {
	if (query_domain(nfd, host, addrs, num_addrs, timeout) == 0)
	    CLOSE_AND_RETURN(0);
	if (h_errno == NETDB_INTERNAL && errno == EINTR)
	    CLOSE_AND_RETURN(-1);
	tried_as_is = 1;
    }

    /*
     * We do at least one level of search if
     *     - there is no dot and RES_DEFNAME is set, or
     *     - there is at least one dot, there is no trailing dot,
     *       and RES_DNSRCH is set.
     */
    if ((!dots && (_res.options & RES_DEFNAMES)) ||
	(dots && !trailing_dot && (_res.options & RES_DNSRCH))) {
	name[n++] = '.';
	for (domain = _res.dnsrch; *domain; domain++) {
	    strncpy(name + n, *domain, maxlen - n);
	    if (query_domain(nfd, name, addrs, num_addrs, timeout) == 0)
		CLOSE_AND_RETURN(0);
	    if (h_errno == NETDB_INTERNAL && errno == EINTR)
		CLOSE_AND_RETURN(-1);
	    if (!(_res.options & RES_DNSRCH))
		break;
	}
    }

    /*
     * If we have not already tried the name "as is", do that now.
     * note that we do this regardless of how many dots were in the
     * name or whether it ends with a dot.
     */
    if (!tried_as_is) {
	if (query_domain(nfd, host, addrs, num_addrs, timeout) == 0)
	    CLOSE_AND_RETURN(0);
    }

    CLOSE_AND_RETURN(-1);
}