root/usr/src/lib/libdhcpagent/common/dhcp_hostconf.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/dhcp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>                     /* memcpy */
#include <fcntl.h>
#include <limits.h>

#include "dhcp_hostconf.h"

static void             relativize_time(DHCP_OPT *, time_t, time_t);
static void             relativize_v6(uint32_t *, time_t, time_t);

/*
 * ifname_to_hostconf(): converts an interface name into a hostconf file for
 *                       that interface
 *
 *   input: const char *: the interface name
 *          boolean_t: B_TRUE if using DHCPv6
 *  output: char *: the hostconf filename
 *    note: uses an internal static buffer (not threadsafe)
 */

char *
ifname_to_hostconf(const char *ifname, boolean_t isv6)
{
        static char filename[sizeof (DHCP_HOSTCONF_TMPL6) + LIFNAMSIZ];

        (void) snprintf(filename, sizeof (filename), "%s%s%s",
            DHCP_HOSTCONF_PREFIX, ifname,
            isv6 ? DHCP_HOSTCONF_SUFFIX6 : DHCP_HOSTCONF_SUFFIX);

        return (filename);
}

/*
 * remove_hostconf(): removes an interface.dhc file
 *
 *   input: const char *: the interface name
 *          boolean_t: B_TRUE if using DHCPv6
 *  output: int: 0 if the file is removed, -1 if it can't be removed
 *          (errno is set)
 */

int
remove_hostconf(const char *ifname, boolean_t isv6)
{
        return (unlink(ifname_to_hostconf(ifname, isv6)));
}

/*
 * read_hostconf(): reads the contents of an <if>.dhc file into a PKT_LIST
 *
 *   input: const char *: the interface name
 *          PKT_LIST **: a pointer to a PKT_LIST * to store the info in
 *          uint_t: the length of the list of PKT_LISTs
 *          boolean_t: B_TRUE if using DHCPv6
 *  output: int: >0 if the file is read and loaded into the PKT_LIST *
 *          successfully, -1 otherwise (errno is set)
 *    note: the PKT and PKT_LISTs are dynamically allocated here
 */

int
read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen,
    boolean_t isv6)
{
        PKT_LIST        *plp = NULL;
        PKT             *pkt = NULL;
        int             fd;
        time_t          orig_time, current_time = time(NULL);
        uint32_t        lease;
        uint32_t        magic;
        int             pcnt = 0;
        int             retval;

        fd = open(ifname_to_hostconf(ifname, isv6), O_RDONLY);
        if (fd == -1)
                return (-1);

        if (read(fd, &magic, sizeof (magic)) != sizeof (magic))
                goto failure;

        if (magic != (isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC))
                goto failure;

        if (read(fd, &orig_time, sizeof (orig_time)) != sizeof (orig_time))
                goto failure;

        /*
         * read the packet back in from disk, and for v4, run it through
         * dhcp_options_scan(). note that we use calloc() because
         * dhcp_options_scan() relies on the structure being zeroed.
         */

        for (pcnt = 0; pcnt < plplen; pcnt++) {

                plp = NULL;
                pkt = NULL;

                if ((plp = calloc(1, sizeof (PKT_LIST))) == NULL)
                        goto failure;

                retval = read(fd, &plp->len, sizeof (plp->len));
                if (retval == 0 && pcnt != 0) {
                        /*
                         * Reached end of file on a boundary, but after
                         * we've read at least one packet, so we consider
                         * this successful, allowing us to use files from
                         * older versions of the agent happily.
                         */
                        free(plp);
                        break;
                } else if (retval != sizeof (plp->len))
                        goto failure;

                if ((pkt = malloc(plp->len)) == NULL)
                        goto failure;

                if (read(fd, pkt, plp->len) != plp->len)
                        goto failure;

                plp->pkt = pkt;

                plpp[pcnt] = plp;

                if (!isv6 && dhcp_options_scan(plp, B_TRUE) != 0)
                        goto failure;

                /*
                 * First packet used to validate that we're interested,
                 * the rest are presumed to be historical reference and
                 * are not relativized
                 */
                if (pcnt == 0)
                        continue;

                if (isv6) {
                        dhcpv6_option_t d6o;
                        dhcpv6_ia_na_t  d6in;
                        dhcpv6_iaaddr_t d6ia;
                        uchar_t         *opts, *optmax, *subomax;

                        /*
                         * Loop over contents of the packet to find the address
                         * options.
                         */
                        opts = (uchar_t *)pkt + sizeof (dhcpv6_message_t);
                        optmax = (uchar_t *)pkt + plp->len;
                        while (opts + sizeof (d6o) <= optmax) {

                                /*
                                 * Extract option header and make sure option
                                 * is intact.
                                 */
                                (void) memcpy(&d6o, opts, sizeof (d6o));
                                d6o.d6o_code = ntohs(d6o.d6o_code);
                                d6o.d6o_len = ntohs(d6o.d6o_len);
                                subomax = opts + sizeof (d6o) + d6o.d6o_len;
                                if (subomax > optmax)
                                        break;

                                /*
                                 * If this isn't an option that contains
                                 * address or prefix leases, then skip over it.
                                 */
                                if (d6o.d6o_code != DHCPV6_OPT_IA_NA &&
                                    d6o.d6o_code != DHCPV6_OPT_IA_TA &&
                                    d6o.d6o_code != DHCPV6_OPT_IA_PD) {
                                        opts = subomax;
                                        continue;
                                }

                                /*
                                 * Handle the option first.
                                 */
                                if (d6o.d6o_code == DHCPV6_OPT_IA_TA) {
                                        /* no timers in this structure */
                                        opts += sizeof (dhcpv6_ia_ta_t);
                                } else {
                                        /* both na and pd */
                                        if (opts + sizeof (d6in) > subomax) {
                                                opts = subomax;
                                                continue;
                                        }
                                        (void) memcpy(&d6in, opts,
                                            sizeof (d6in));
                                        relativize_v6(&d6in.d6in_t1, orig_time,
                                            current_time);
                                        relativize_v6(&d6in.d6in_t2, orig_time,
                                            current_time);
                                        (void) memcpy(opts, &d6in,
                                            sizeof (d6in));
                                        opts += sizeof (d6in);
                                }

                                /*
                                 * Now handle each suboption (address) inside.
                                 */
                                while (opts + sizeof (d6o) <= subomax) {
                                        /*
                                         * Verify the suboption header first.
                                         */
                                        (void) memcpy(&d6o, opts,
                                            sizeof (d6o));
                                        d6o.d6o_code = ntohs(d6o.d6o_code);
                                        d6o.d6o_len = ntohs(d6o.d6o_len);
                                        if (opts + sizeof (d6o) + d6o.d6o_len >
                                            subomax)
                                                break;
                                        if (d6o.d6o_code != DHCPV6_OPT_IAADDR) {
                                                opts += sizeof (d6o) +
                                                    d6o.d6o_len;
                                                continue;
                                        }

                                        /*
                                         * Now process the contents.
                                         */
                                        if (opts + sizeof (d6ia) > subomax)
                                                break;
                                        (void) memcpy(&d6ia, opts,
                                            sizeof (d6ia));
                                        relativize_v6(&d6ia.d6ia_preflife,
                                            orig_time, current_time);
                                        relativize_v6(&d6ia.d6ia_vallife,
                                            orig_time, current_time);
                                        (void) memcpy(opts, &d6ia,
                                            sizeof (d6ia));
                                        opts += sizeof (d6o) + d6o.d6o_len;
                                }
                                opts = subomax;
                        }
                } else {

                        /*
                         * make sure the IPv4 DHCP lease is still valid.
                         */

                        if (plp->opts[CD_LEASE_TIME] != NULL &&
                            plp->opts[CD_LEASE_TIME]->len ==
                            sizeof (lease_t)) {

                                (void) memcpy(&lease,
                                    plp->opts[CD_LEASE_TIME]->value,
                                    sizeof (lease_t));

                                lease = ntohl(lease);
                                if ((lease != DHCP_PERM) &&
                                    (orig_time + lease) <= current_time)
                                        goto failure;
                        }

                        relativize_time(plp->opts[CD_T1_TIME], orig_time,
                            current_time);
                        relativize_time(plp->opts[CD_T2_TIME], orig_time,
                            current_time);
                        relativize_time(plp->opts[CD_LEASE_TIME], orig_time,
                            current_time);
                }
        }

        (void) close(fd);
        return (pcnt);

failure:
        free(pkt);
        free(plp);
        while (pcnt-- > 0) {
                free(plpp[pcnt]->pkt);
                free(plpp[pcnt]);
        }
        (void) close(fd);
        return (-1);
}

/*
 * write_hostconf(): writes the contents of a PKT_LIST into an <if>.dhc file
 *
 *   input: const char *: the interface name
 *          PKT_LIST **: a list of pointers to PKT_LIST to write
 *          uint_t: length of the list of PKT_LIST pointers
 *          time_t: a starting time to treat the relative lease times
 *                  in the first packet as relative to
 *          boolean_t: B_TRUE if using DHCPv6
 *  output: int: 0 if the file is written successfully, -1 otherwise
 *          (errno is set)
 */

int
write_hostconf(
    const char *ifname,
    PKT_LIST *pl[],
    uint_t pllen,
    time_t relative_to,
    boolean_t isv6)
{
        int             fd;
        struct iovec    iov[IOV_MAX];
        int             retval;
        uint32_t        magic;
        ssize_t         explen = 0; /* Expected length of write */
        int             i, iovlen = 0;

        fd = open(ifname_to_hostconf(ifname, isv6), O_WRONLY|O_CREAT|O_TRUNC,
            0600);
        if (fd == -1)
                return (-1);

        /*
         * first write our magic number, then the relative time of the
         * leases, then for each packet we write the length of the packet
         * followed by the packet.  we will then use the relative time in
         * read_hostconf() to recalculate the lease times for the first packet.
         */

        magic = isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC;
        iov[iovlen].iov_base = (caddr_t)&magic;
        explen += iov[iovlen++].iov_len  = sizeof (magic);
        iov[iovlen].iov_base = (caddr_t)&relative_to;
        explen += iov[iovlen++].iov_len  = sizeof (relative_to);
        for (i = 0; i < pllen && iovlen < (IOV_MAX - 1); i++) {
                iov[iovlen].iov_base = (caddr_t)&pl[i]->len;
                explen += iov[iovlen++].iov_len  = sizeof (pl[i]->len);
                iov[iovlen].iov_base = (caddr_t)pl[i]->pkt;
                explen += iov[iovlen++].iov_len  = pl[i]->len;
        }

        retval = writev(fd, iov, iovlen);

        (void) close(fd);

        if (retval != explen)
                return (-1);

        return (0);
}

/*
 * relativize_time(): re-relativizes a time in a DHCP option
 *
 *   input: DHCP_OPT *: the DHCP option parameter to convert
 *          time_t: the time the leases in the packet are currently relative to
 *          time_t: the current time which leases will become relative to
 *  output: void
 */

static void
relativize_time(DHCP_OPT *option, time_t orig_time, time_t current_time)
{
        uint32_t        pkt_time;
        time_t          time_diff = current_time - orig_time;

        if (option == NULL || option->len != sizeof (lease_t))
                return;

        (void) memcpy(&pkt_time, option->value, option->len);
        if (ntohl(pkt_time) != DHCP_PERM)
                pkt_time = htonl(ntohl(pkt_time) - time_diff);

        (void) memcpy(option->value, &pkt_time, option->len);
}

/*
 * relativize_v6(): re-relativizes a time in a DHCPv6 option
 *
 *   input: uint32_t *: the time value to convert
 *          time_t: the time the leases in the packet are currently relative to
 *          time_t: the current time which leases will become relative to
 *  output: void
 */

static void
relativize_v6(uint32_t *val, time_t orig_time, time_t current_time)
{
        uint32_t        hval;
        time_t          time_diff = current_time - orig_time;

        hval = ntohl(*val);
        if (hval != DHCPV6_INFTIME) {
                if (hval < time_diff)
                        *val = 0;
                else
                        *val = htonl(hval - time_diff);
        }
}