/*
 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1996,1999 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "validator-internal.h"

#include "res_support.h"
#include "res_comp.h"

#if HAVE_ARPA_NAMESER_H && !defined(__OpenBSD__) && !defined(eabi)
#ifndef STRUCT___NS_MSG_HAS__MSG_PTR
#   ifdef STRUCT___NS_MSG_HAS__PTR
#      define _msg_ptr _ptr
#   else
#      error "unknown msg ptr member in struct __ns_msg"
#   endif
#endif
#endif

/*
 * Forward. 
 */

static void     setsection(ns_msg * msg, ns_sect sect);

/*
 * Macros. 
 */

#define RETERR(err) do { errno = (err); return (-1); } while (0)

/*
 * Public. 
 */

/*
 * These need to be in the same order as the nres.h:ns_flag enum. 
 */
struct _ns_flagdata _ns_flagdata_flags[16] = {
    {0x8000, 15},               /* qr. */
    {0x7800, 11},               /* opcode. */
    {0x0400, 10},               /* aa. */
    {0x0200, 9},                /* tc. */
    {0x0100, 8},                /* rd. */
    {0x0080, 7},                /* ra. */
    {0x0040, 6},                /* z. */
    {0x0020, 5},                /* ad. */
    {0x0010, 4},                /* cd. */
    {0x000f, 0},                /* rcode. */
    {0x0000, 0},                /* expansion (1/6). */
    {0x0000, 0},                /* expansion (2/6). */
    {0x0000, 0},                /* expansion (3/6). */
    {0x0000, 0},                /* expansion (4/6). */
    {0x0000, 0},                /* expansion (5/6). */
    {0x0000, 0},                /* expansion (6/6). */
};

/*
 * ns_msg_getflag is a macro on linux, but Solaris and Darwin
 * both use defines of the function to map to other function
 * names.
 *
 * We rename this function for internal usage
 */
int
libsres_msg_getflag(ns_msg han, int flag)
{
    return (((han)._flags & _ns_flagdata_flags[flag].mask) >> _ns_flagdata_flags[flag].
            shift);
}

int
ns_skiprr(const u_char * ptr, const u_char * eom, ns_sect section,
          int count)
{
    const u_char   *optr = ptr;

    for ((void) NULL; count > 0; count--) {
        int             b, rdlength;

        b = dn_skipname(ptr, eom);
        if (b < 0)
            RETERR(EMSGSIZE);
        ptr +=
            b /*Name */  + NS_INT16SZ /*Type */  + NS_INT16SZ /*Class */ ;
        if (section != ns_s_qd) {
            if (ptr + NS_INT32SZ + NS_INT16SZ > eom)
                RETERR(EMSGSIZE);
            ptr += NS_INT32SZ /*TTL*/;
            RES_GET16(rdlength, ptr);
            ptr += rdlength /*RData */ ;
        }
    }
    if (ptr > eom)
        RETERR(EMSGSIZE);
    return (ptr - optr);
}

int
ns_initparse(const u_char * msg, int msglen, ns_msg * handle)
{
    const u_char   *eom = msg + msglen;
    int             i;

    if ((NULL == msg) || (0 == msglen))
#ifdef ENODATA
        RETERR(ENODATA);
#else
        RETERR(EINVAL);
#endif
    memset(handle, 0x5e, sizeof(*handle));
    handle->_msg = msg;
    handle->_eom = eom;
    if (msg + NS_INT16SZ > eom)
        RETERR(EMSGSIZE);
    RES_GET16(handle->_id, msg);
    if (msg + NS_INT16SZ > eom)
        RETERR(EMSGSIZE);
    RES_GET16(handle->_flags, msg);
    for (i = 0; i < ns_s_max; i++) {
        if (msg + NS_INT16SZ > eom)
            RETERR(EMSGSIZE);
        RES_GET16(handle->_counts[i], msg);
    }
    for (i = 0; i < ns_s_max; i++)
        if (handle->_counts[i] == 0)
            handle->_sections[i] = NULL;
        else {
            int             b = ns_skiprr(msg, eom, (ns_sect) i,
                                          handle->_counts[i]);

            if (b < 0)
                return (-1);
            handle->_sections[i] = msg;
            msg += b;
        }
    if (msg != eom)
        RETERR(EMSGSIZE);
    setsection(handle, ns_s_max);
    return (0);
}

int
ns_parserr(ns_msg * handle, ns_sect section, int rrnum, ns_rr * rr)
{
    int             b;
    int             tmp;

    /*
     * Make section right. 
     */
    if ((tmp = section) < 0 || section >= ns_s_max)
        RETERR(ENODEV);
    if (section != handle->_sect)
        setsection(handle, section);

    /*
     * Make rrnum right. 
     */
    if (rrnum == -1)
        rrnum = handle->_rrnum;
    if (rrnum < 0 || rrnum >= handle->_counts[(int) section])
        RETERR(ENODEV);
    if (rrnum < handle->_rrnum)
        setsection(handle, section);
    if (rrnum > handle->_rrnum) {
        b = ns_skiprr(handle->_msg_ptr, handle->_eom, section,
                      rrnum - handle->_rrnum);

        if (b < 0)
            return (-1);
        handle->_msg_ptr += b;
        handle->_rrnum = rrnum;
    }

    /*
     * Do the parse. 
     */
    b = dn_expand(handle->_msg, handle->_eom,
                  handle->_msg_ptr, rr->name, NS_MAXDNAME);
    if (b < 0)
        return (-1);
    handle->_msg_ptr += b;
    if (handle->_msg_ptr + NS_INT16SZ + NS_INT16SZ > handle->_eom)
        RETERR(EMSGSIZE);
    RES_GET16(rr->type, handle->_msg_ptr);
    RES_GET16(rr->rr_class, handle->_msg_ptr);
    if (section == ns_s_qd) {
        rr->ttl = 0;
        rr->rdlength = 0;
        rr->rdata = NULL;
    } else {
        if (handle->_msg_ptr + NS_INT32SZ + NS_INT16SZ > handle->_eom)
            RETERR(EMSGSIZE);
        RES_GET32(rr->ttl, handle->_msg_ptr);
        RES_GET16(rr->rdlength, handle->_msg_ptr);
        if (handle->_msg_ptr + rr->rdlength > handle->_eom)
            RETERR(EMSGSIZE);
        rr->rdata = handle->_msg_ptr;
        handle->_msg_ptr += rr->rdlength;
    }
    if (++handle->_rrnum > handle->_counts[(int) section])
        setsection(handle, (ns_sect) ((int) section + 1));

    /*
     * All done. 
     */
    return (0);
}

/*
 * Private. 
 */

static void
setsection(ns_msg * msg, ns_sect sect)
{
    msg->_sect = sect;
    if (sect == ns_s_max) {
        msg->_rrnum = -1;
        msg->_msg_ptr = NULL;
    } else {
        msg->_rrnum = 0;
        msg->_msg_ptr = msg->_sections[(int) sect];
    }
}
