root/usr/src/lib/libresolv2/common/nameser/ns_newmsg.c
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or 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 <port_before.h>

#include <arpa/nameser.h>

#include <assert.h>
#include <errno.h>
#include <string.h>

#include <port_after.h>

static int      rdcpy(ns_newmsg *, ns_type, const u_char *, size_t);

/* Initialize a "newmsg" object to empty.
 */
int
ns_newmsg_init(u_char *buffer, size_t bufsiz, ns_newmsg *handle) {
        ns_msg *msg = &handle->msg;

        memset(handle, 0, sizeof *handle);
        msg->_msg = buffer;
        msg->_eom = buffer + bufsiz;
        msg->_sect = ns_s_qd;
        msg->_rrnum = 0;
        msg->_msg_ptr = buffer + NS_HFIXEDSZ;
        handle->dnptrs[0] = msg->_msg;
        handle->dnptrs[1] = NULL;
        handle->lastdnptr = &handle->dnptrs[sizeof handle->dnptrs /
                                            sizeof handle->dnptrs[0] - 1];
        return (0);
}

/* Initialize a "newmsg" object by copying an existing parsed message.
 */
int
ns_newmsg_copy(ns_newmsg *handle, ns_msg *msg) {
        ns_flag flag;
        ns_sect sect;

        ns_newmsg_id(handle, ns_msg_id(*msg));
        for (flag = ns_f_qr; flag < ns_f_max; flag++)
                ns_newmsg_flag(handle, flag, ns_msg_getflag(*msg, flag));
        for (sect = ns_s_qd; sect < ns_s_max; sect++) {
                int i, count;

                count = ns_msg_count(*msg, sect);
                for (i = 0; i < count; i++) {
                        ns_rr2 rr;
                        int x;

                        if (ns_parserr2(msg, sect, i, &rr) < 0)
                                return (-1);
                        if (sect == ns_s_qd)
                                x = ns_newmsg_q(handle,
                                                ns_rr_nname(rr),
                                                ns_rr_type(rr),
                                                ns_rr_class(rr));
                        else
                                x = ns_newmsg_rr(handle, sect,
                                                 ns_rr_nname(rr),
                                                 ns_rr_type(rr),
                                                 ns_rr_class(rr),
                                                 ns_rr_ttl(rr),
                                                 ns_rr_rdlen(rr),
                                                 ns_rr_rdata(rr));
                        if (x < 0)
                                return (-1);
                }
        }
        return (0);
}

/* Set the message-ID in a "newmsg" object.
 */
void
ns_newmsg_id(ns_newmsg *handle, u_int16_t id) {
        ns_msg *msg = &handle->msg;

        msg->_id = id;
}

/* Set a flag (including rcode or opcode) in a "newmsg" object.
 */
void
ns_newmsg_flag(ns_newmsg *handle, ns_flag flag, u_int value) {
        extern struct _ns_flagdata _ns_flagdata[16];
        struct _ns_flagdata *fd = &_ns_flagdata[flag];
        ns_msg *msg = &handle->msg;

        assert(flag < ns_f_max);
        msg->_flags &= (~fd->mask);
        msg->_flags |= (value << fd->shift);
}

/* Add a question (or zone, if it's an update) to a "newmsg" object.
 */
int
ns_newmsg_q(ns_newmsg *handle, ns_nname_ct qname,
            ns_type qtype, ns_class qclass)
{
        ns_msg *msg = &handle->msg;
        u_char *t;
        int n;

        if (msg->_sect != ns_s_qd) {
                errno = ENODEV;
                return (-1);
        }
        t = (u_char *) (unsigned long) msg->_msg_ptr;
        if (msg->_rrnum == 0)
                msg->_sections[ns_s_qd] = t;
        n = ns_name_pack(qname, t, msg->_eom - t,
                         handle->dnptrs, handle->lastdnptr);
        if (n < 0)
                return (-1);
        t += n;
        if (t + QFIXEDSZ >= msg->_eom) {
                errno = EMSGSIZE;
                return (-1);
        }
        NS_PUT16(qtype, t);
        NS_PUT16(qclass, t);
        msg->_msg_ptr = t;
        msg->_counts[ns_s_qd] = ++msg->_rrnum;
        return (0);
}

/* Add an RR to a "newmsg" object.
 */
int
ns_newmsg_rr(ns_newmsg *handle, ns_sect sect,
             ns_nname_ct name, ns_type type,
             ns_class rr_class, u_int32_t ttl,
             u_int16_t rdlen, const u_char *rdata)
{
        ns_msg *msg = &handle->msg;
        u_char *t;
        int n;

        if (sect < msg->_sect) {
                errno = ENODEV;
                return (-1);
        }
        t = (u_char *) (unsigned long) msg->_msg_ptr;
        if (sect > msg->_sect) {
                msg->_sect = sect;
                msg->_sections[sect] = t;
                msg->_rrnum = 0;
        }
        n = ns_name_pack(name, t, msg->_eom - t,
                         handle->dnptrs, handle->lastdnptr);
        if (n < 0)
                return (-1);
        t += n;
        if (t + RRFIXEDSZ + rdlen >= msg->_eom) {
                errno = EMSGSIZE;
                return (-1);
        }
        NS_PUT16(type, t);
        NS_PUT16(rr_class, t);
        NS_PUT32(ttl, t);
        msg->_msg_ptr = t;
        if (rdcpy(handle, type, rdata, rdlen) < 0)
                return (-1);
        msg->_counts[sect] = ++msg->_rrnum;
        return (0);
}

/* Complete a "newmsg" object and return its size for use in write().
 * (Note: the "newmsg" object is also made ready for ns_parserr() etc.)
 */
size_t
ns_newmsg_done(ns_newmsg *handle) {
        ns_msg *msg = &handle->msg;
        ns_sect sect;
        u_char *t;

        t = (u_char *) (unsigned long) msg->_msg;
        NS_PUT16(msg->_id, t);
        NS_PUT16(msg->_flags, t);
        for (sect = 0; sect < ns_s_max; sect++)
                NS_PUT16(msg->_counts[sect], t);
        msg->_eom = msg->_msg_ptr;
        msg->_sect = ns_s_max;
        msg->_rrnum = -1;
        msg->_msg_ptr = NULL;
        return (msg->_eom - msg->_msg);
}

/* Private. */

/* Copy an RDATA, using compression pointers where RFC1035 permits.
 */
static int
rdcpy(ns_newmsg *handle, ns_type type, const u_char *rdata, size_t rdlen) {
        ns_msg *msg = &handle->msg;
        u_char *p = (u_char *) (unsigned long) msg->_msg_ptr;
        u_char *t = p + NS_INT16SZ;
        u_char *s = t;
        int n;

        switch (type) {
        case ns_t_soa:
                /* MNAME. */
                n = ns_name_pack(rdata, t, msg->_eom - t,
                                 handle->dnptrs, handle->lastdnptr);
                if (n < 0)
                        return (-1);
                t += n;
                if (ns_name_skip(&rdata, msg->_eom) < 0)
                        return (-1);

                /* ANAME. */
                n = ns_name_pack(rdata, t, msg->_eom - t,
                                 handle->dnptrs, handle->lastdnptr);
                if (n < 0)
                        return (-1);
                t += n;
                if (ns_name_skip(&rdata, msg->_eom) < 0)
                        return (-1);

                /* Serial, Refresh, Retry, Expiry, and Minimum. */
                if ((msg->_eom - t) < (NS_INT32SZ * 5)) {
                        errno = EMSGSIZE;
                        return (-1);
                }
                memcpy(t, rdata, NS_INT32SZ * 5);
                t += (NS_INT32SZ * 5);
                break;
        case ns_t_ptr:
        case ns_t_cname:
        case ns_t_ns:
                /* PTRDNAME, CNAME, or NSDNAME. */
                n = ns_name_pack(rdata, t, msg->_eom - t,
                                 handle->dnptrs, handle->lastdnptr);
                if (n < 0)
                        return (-1);
                t += n;
                break;
        default:
                memcpy(t, rdata, rdlen);
                t += rdlen;
        }
        NS_PUT16(t - s, p);
        msg->_msg_ptr = t;
        return (0);
}