root/usr/src/cmd/cmd-inet/lib/ipmgmtd/ipmgmt_door.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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
 * Copyright 2021, Tintri by DDN. All rights reserved.
 * Copyright 2022, Oxide Computer Company.
 */

/*
 * Main door handler functions used by ipmgmtd to process the different door
 * call requests, issued by the library libipadm.so.
 */

#include <alloca.h>
#include <pwd.h>
#include <auth_attr.h>
#include <secdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <assert.h>
#include <libnvpair.h>
#include "ipmgmt_impl.h"


static void ipmgmt_common_handler(char *, char *, db_wfunc_t *);

/* Handler declaration for each door command */
typedef void ipmgmt_door_handler_t(void *argp);

static ipmgmt_door_handler_t    ipmgmt_getaddr_handler,
                                ipmgmt_getprop_handler,
                                ipmgmt_getif_handler,
                                ipmgmt_initif_handler,
                                ipmgmt_aobjop_handler,
                                ipmgmt_resetaddr_handler,
                                ipmgmt_setif_handler,
                                ipmgmt_resetif_handler,
                                ipmgmt_resetprop_handler,
                                ipmgmt_setaddr_handler,
                                ipmgmt_setprop_handler,
                                ipmgmt_ipmp_update_handler;

typedef struct ipmgmt_door_info_s {
        uint_t                  idi_cmd;
        boolean_t               idi_set;
        ipmgmt_door_handler_t   *idi_handler;
} ipmgmt_door_info_t;

/* maps door commands to door handler functions */
static ipmgmt_door_info_t i_ipmgmt_door_info_tbl[] = {
        { IPMGMT_CMD_SETPROP,           B_TRUE,  ipmgmt_setprop_handler },
        { IPMGMT_CMD_SETIF,             B_TRUE,  ipmgmt_setif_handler },
        { IPMGMT_CMD_SETADDR,           B_TRUE,  ipmgmt_setaddr_handler },
        { IPMGMT_CMD_GETPROP,           B_FALSE, ipmgmt_getprop_handler },
        { IPMGMT_CMD_GETIF,             B_FALSE, ipmgmt_getif_handler },
        { IPMGMT_CMD_GETADDR,           B_FALSE, ipmgmt_getaddr_handler },
        { IPMGMT_CMD_RESETIF,           B_TRUE,  ipmgmt_resetif_handler },
        { IPMGMT_CMD_RESETADDR,         B_TRUE,  ipmgmt_resetaddr_handler },
        { IPMGMT_CMD_RESETPROP,         B_TRUE,  ipmgmt_resetprop_handler },
        { IPMGMT_CMD_INITIF,            B_TRUE,  ipmgmt_initif_handler },
        { IPMGMT_CMD_ADDROBJ_LOOKUPADD, B_TRUE,  ipmgmt_aobjop_handler },
        { IPMGMT_CMD_ADDROBJ_SETLIFNUM, B_TRUE,  ipmgmt_aobjop_handler },
        { IPMGMT_CMD_ADDROBJ_ADD,       B_TRUE,  ipmgmt_aobjop_handler },
        { IPMGMT_CMD_AOBJNAME2ADDROBJ,  B_FALSE, ipmgmt_aobjop_handler },
        { IPMGMT_CMD_LIF2ADDROBJ,       B_FALSE, ipmgmt_aobjop_handler },
        { IPMGMT_CMD_IPMP_UPDATE,       B_FALSE, ipmgmt_ipmp_update_handler},
        { 0, 0, NULL },
};

/*
 * The main server procedure function that gets invoked for any of the incoming
 * door commands. Inside this function we identify the incoming command and
 * invoke the right door handler function.
 */
/* ARGSUSED */
void
ipmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
    uint_t n_desc)
{
        ipmgmt_door_info_t      *infop = NULL;
        ipmgmt_retval_t         retval;
        int                     i;
        uint_t                  err;
        ucred_t                 *cred = NULL;

        for (i = 0; i_ipmgmt_door_info_tbl[i].idi_cmd != 0; i++) {
                if (i_ipmgmt_door_info_tbl[i].idi_cmd ==
                    ((ipmgmt_arg_t *)(void *)argp)->ia_cmd) {
                        infop = &i_ipmgmt_door_info_tbl[i];
                        break;
                }
        }

        if (infop == NULL) {
                ipmgmt_log(LOG_ERR, "Invalid door command specified");
                err = EINVAL;
                goto fail;
        }

        /* check for solaris.network.interface.config authorization */
        if (infop->idi_set) {
                uid_t           uid;
                struct passwd   pwd;
                char            buf[1024];

                if (door_ucred(&cred) != 0) {
                        err = errno;
                        ipmgmt_log(LOG_ERR, "Could not get user credentials.");
                        goto fail;
                }
                uid = ucred_getruid(cred);
                if ((int)uid < 0) {
                        err = errno;
                        ipmgmt_log(LOG_ERR, "Could not get user id.");
                        goto fail;
                }
                if (getpwuid_r(uid, &pwd, buf, sizeof (buf)) ==
                    NULL) {
                        err = errno;
                        ipmgmt_log(LOG_ERR, "Could not get password entry.");
                        goto fail;
                }
                if (chkauthattr(NETWORK_INTERFACE_CONFIG_AUTH,
                    pwd.pw_name) != 1) {
                        err = EPERM;
                        ipmgmt_log(LOG_ERR, "Not authorized for operation.");
                        goto fail;
                }
                ucred_free(cred);
        }

        /* individual handlers take care of calling door_return */
        infop->idi_handler((void *)argp);
        return;
fail:
        ucred_free(cred);
        retval.ir_err = err;
        (void) door_return((char *)&retval, sizeof (retval), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_GETPROP. It retrieves the persisted
 * property value for the given property.
 */
static void
ipmgmt_getprop_handler(void *argp)
{
        ipmgmt_prop_arg_t       *pargp = argp;
        ipmgmt_getprop_rval_t   rval, *rvalp = &rval;

        assert(pargp->ia_cmd == IPMGMT_CMD_GETPROP);

        rvalp->ir_err = ipmgmt_db_walk(ipmgmt_db_getprop, pargp, IPADM_DB_READ);
        if (rvalp->ir_err == 0)
                (void) strlcpy(rvalp->ir_pval, pargp->ia_pval,
                    sizeof (rvalp->ir_pval));
        (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_SETPROP. It persists the property value
 * for the given property in the DB.
 */
static void
ipmgmt_setprop_handler(void *argp)
{
        ipmgmt_prop_arg_t       *pargp = argp;
        ipmgmt_retval_t         rval;
        ipadm_dbwrite_cbarg_t   cb;
        nvlist_t                *nvl = NULL;
        int                     err;

        assert(pargp->ia_cmd == IPMGMT_CMD_SETPROP);

        if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
                goto fail;
        if (pargp->ia_module[0] != '\0' &&
            (err = nvlist_add_string(nvl, IPADM_NVP_PROTONAME,
            pargp->ia_module)) != 0) {
                goto fail;
        }
        if (pargp->ia_ifname[0] != '\0' &&
            (err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
            pargp->ia_ifname)) != 0)
                goto fail;
        if (pargp->ia_aobjname[0] != '\0' &&
            (err = nvlist_add_string(nvl, IPADM_NVP_AOBJNAME,
            pargp->ia_aobjname)) != 0)
                goto fail;
        if ((err = nvlist_add_string(nvl, pargp->ia_pname,
            pargp->ia_pval)) != 0)
                goto fail;

        cb.dbw_nvl = nvl;
        cb.dbw_flags = pargp->ia_flags;
        err = ipmgmt_db_walk(ipmgmt_db_update, &cb, IPADM_DB_WRITE);
fail:
        nvlist_free(nvl);
        rval.ir_err = err;
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Helper function for ipmgmt_setaddr_handler().
 * It converts the nvlist_t, `nvl', to aobjmap node `nodep'.
 */
static int
i_ipmgmt_nvl2aobjnode(nvlist_t *nvl, ipmgmt_aobjmap_t *nodep)
{
        char                    *aobjname = NULL, *ifname = NULL;
        int32_t                 lnum;
        nvlist_t                *nvladdr;
        sa_family_t             af = AF_UNSPEC;
        ipadm_addr_type_t       addrtype = IPADM_ADDR_NONE;
        int                     err = 0;

        /*
         * Retrieve all the information needed to build '*nodep' from
         * nvlist_t nvl.
         */
        if ((err = nvlist_lookup_string(nvl, IPADM_NVP_AOBJNAME,
            &aobjname)) != 0 ||
            (err = nvlist_lookup_string(nvl, IPADM_NVP_IFNAME, &ifname)) != 0 ||
            (err = nvlist_lookup_int32(nvl, IPADM_NVP_LIFNUM, &lnum)) != 0) {
                return (err);
        }
        if (nvlist_exists(nvl, IPADM_NVP_IPV4ADDR)) {
                af = AF_INET;
                addrtype = IPADM_ADDR_STATIC;
        } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_DHCP, &nvladdr) == 0) {
                char    *reqhost;

                af = AF_INET;
                addrtype = IPADM_ADDR_DHCP;

                /*
                 * ipmgmt_am_reqhost comes through in `nvl' for purposes of
                 * updating the cached representation, but it is persisted as
                 * a stand-alone DB line; so remove it after copying it.
                 */
                if (!nvlist_exists(nvl, IPADM_NVP_REQHOST)) {
                        *nodep->ipmgmt_am_reqhost = '\0';
                } else {
                        if ((err = nvlist_lookup_string(nvl, IPADM_NVP_REQHOST,
                            &reqhost)) != 0)
                                return (err);

                        (void) strlcpy(nodep->ipmgmt_am_reqhost, reqhost,
                            sizeof (nodep->ipmgmt_am_reqhost));
                        (void) nvlist_remove(nvl, IPADM_NVP_REQHOST,
                            DATA_TYPE_STRING);
                }
        } else if (nvlist_exists(nvl, IPADM_NVP_IPV6ADDR)) {
                af = AF_INET6;
                addrtype = IPADM_ADDR_STATIC;
        } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_INTFID, &nvladdr) == 0) {
                struct sockaddr_in6             sin6 = {0};
                uint8_t *addr6;
                uint32_t plen;
                uint_t  n;

                af = AF_INET6;
                addrtype = IPADM_ADDR_IPV6_ADDRCONF;
                if (nvlist_lookup_uint32(nvladdr, IPADM_NVP_PREFIXLEN,
                    &plen) != 0)
                        return (EINVAL);
                if (plen != 0) {
                        if (nvlist_lookup_uint8_array(nvladdr,
                            IPADM_NVP_IPNUMADDR, &addr6, &n) != 0)
                                return (EINVAL);
                        bcopy(addr6, &sin6.sin6_addr, n);
                }

                nodep->ipmgmt_am_linklocal = B_TRUE;
                nodep->ipmgmt_am_ifid = sin6;
        }

        /*
         * populate the non-addrtype-specific `*nodep' with retrieved values.
         */
        (void) strlcpy(nodep->am_ifname, ifname, sizeof (nodep->am_ifname));
        (void) strlcpy(nodep->am_aobjname, aobjname,
            sizeof (nodep->am_aobjname));
        nodep->am_lnum = lnum;
        nodep->am_family = af;
        nodep->am_atype = addrtype;
        nodep->am_next = NULL;

        /*
         * Do not store logical interface number in persistent store as it
         * takes different value on reboot. So remove it from `nvl'.
         */
        if (nvlist_exists(nvl, IPADM_NVP_LIFNUM))
                (void) nvlist_remove(nvl, IPADM_NVP_LIFNUM, DATA_TYPE_INT32);

        return (0);
}

/*
 * Handles the door command IPMGMT_CMD_SETADDR. It adds a new address object
 * node to the list `aobjmap' and optionally persists the address
 * information in the DB.
 */
static void
ipmgmt_setaddr_handler(void *argp)
{
        ipmgmt_setaddr_arg_t    *sargp = argp;
        ipmgmt_retval_t         rval;
        ipmgmt_aobjmap_t        node = {0};
        nvlist_t                *nvl = NULL;
        char                    *nvlbuf;
        size_t                  nvlsize = sargp->ia_nvlsize;
        uint32_t                flags = sargp->ia_flags;
        int                     err = 0;

        nvlbuf = (char *)argp + sizeof (ipmgmt_setaddr_arg_t);
        if ((err = nvlist_unpack(nvlbuf, nvlsize, &nvl, 0)) != 0)
                goto ret;
        if (flags & (IPMGMT_ACTIVE|IPMGMT_INIT)) {
                if ((err = i_ipmgmt_nvl2aobjnode(nvl, &node)) != 0)
                        goto ret;
                if (flags & IPMGMT_INIT)
                        node.am_flags = (IPMGMT_ACTIVE|IPMGMT_PERSIST);
                else
                        node.am_flags = flags & ~IPMGMT_PROPS_ONLY;
                if ((err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD)) != 0)
                        goto ret;
        }
        if ((flags & IPMGMT_PERSIST) && !(flags & IPMGMT_PROPS_ONLY)) {
                ipadm_dbwrite_cbarg_t   cb;

                cb.dbw_nvl = nvl;
                cb.dbw_flags = 0;
                err = ipmgmt_db_walk(ipmgmt_db_add, &cb, IPADM_DB_WRITE);
        }
ret:
        nvlist_free(nvl);
        rval.ir_err = err;
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Handles the door commands that read or modify the `aobjmap' structure.
 *
 * IPMGMT_CMD_ADDROBJ_LOOKUPADD - places a stub address object in `aobjmap'
 *      after ensuring that the namespace is not taken. If required, also
 *      generates an `aobjname' for address object for the library to use.
 * IPMGMT_CMD_ADDROBJ_ADD - add/update address object in `aobjmap'
 * IPMGMT_CMD_LIF2ADDROBJ - given a logical interface, return address object
 *      associated with that logical interface.
 * IPMGMT_CMD_AOBJNAME2ADDROBJ - given an address object name return logical
 *      interface associated with that address object.
 */
static void
ipmgmt_aobjop_handler(void *argp)
{
        ipmgmt_aobjop_arg_t     *largp = argp;
        ipmgmt_retval_t         rval;
        ipmgmt_aobjop_rval_t    aobjrval;
        void                    *rvalp;
        size_t                  rsize;
        ipmgmt_aobjmap_t        node;
        int                     err = 0;
        char                    *ifname = largp->ia_ifname;
        char                    *aobjname = largp->ia_aobjname;
        int32_t                 lnum = largp->ia_lnum;
        sa_family_t             af = largp->ia_family;
        ipadm_addr_type_t       atype = largp->ia_atype;
        ipmgmt_aobjmap_t        *head;

        bzero(&aobjrval, sizeof (aobjrval));
        switch (largp->ia_cmd) {
        case IPMGMT_CMD_ADDROBJ_LOOKUPADD:
                rsize = sizeof (ipmgmt_aobjop_rval_t);
                rvalp = &aobjrval;
                bzero(&node, sizeof (node));
                (void) strlcpy(node.am_aobjname, aobjname,
                    sizeof (node.am_aobjname));
                (void) strlcpy(node.am_ifname, ifname,
                    sizeof (node.am_ifname));
                node.am_family = af;
                node.am_atype = atype;
                if (atype == IPADM_ADDR_IPV6_ADDRCONF) {
                        node.ipmgmt_am_linklocal = B_TRUE;
                }
                /* no logical number is associated with this addrobj yet */
                node.am_lnum = -1;
                /* The address object is not persisted yet. */
                node.am_flags = IPMGMT_ACTIVE;
                err = ipmgmt_aobjmap_op(&node, ADDROBJ_LOOKUPADD);
                if (err == 0) {
                        aobjrval.ir_lnum = node.am_lnum;
                        (void) strlcpy(aobjrval.ir_aobjname, node.am_aobjname,
                            sizeof (aobjrval.ir_aobjname));
                }
                break;
        case IPMGMT_CMD_ADDROBJ_SETLIFNUM:
                rsize = sizeof (ipmgmt_retval_t);
                rvalp = &rval;
                bzero(&node, sizeof (node));
                (void) strlcpy(node.am_aobjname, aobjname,
                    sizeof (node.am_aobjname));
                (void) strlcpy(node.am_ifname, ifname,
                    sizeof (node.am_ifname));
                node.am_family = af;
                node.am_lnum = lnum;
                err = ipmgmt_aobjmap_op(&node, ADDROBJ_SETLIFNUM);
                break;
        case IPMGMT_CMD_ADDROBJ_ADD:
                rsize = sizeof (ipmgmt_retval_t);
                rvalp = &rval;
                if (aobjname[0] == '\0' || ifname[0] == '\0' || lnum == -1 ||
                    af == AF_UNSPEC) {
                        err = EINVAL;
                        break;
                }
                bzero(&node, sizeof (node));
                (void) strlcpy(node.am_aobjname, aobjname,
                    sizeof (node.am_aobjname));
                (void) strlcpy(node.am_ifname, ifname,
                    sizeof (node.am_ifname));
                node.am_atype = atype;
                node.am_lnum = lnum;
                node.am_family = af;
                /* The address object is not persisted. */
                node.am_flags = IPMGMT_ACTIVE;
                err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD);
                break;
        case IPMGMT_CMD_AOBJNAME2ADDROBJ:
                rsize = sizeof (ipmgmt_aobjop_rval_t);
                rvalp = &aobjrval;
                bzero(&aobjrval, sizeof (aobjrval));
                if (aobjname[0] == '\0') {
                        err = EINVAL;
                        break;
                }
                (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock);
                head = aobjmap.aobjmap_head;
                for (; head; head = head->am_next) {
                        if (strcmp(head->am_aobjname, aobjname) != 0)
                                continue;
                        /*
                         * For an auto-configured interface, return
                         * the lifnum that has the link-local on it.
                         * Other logical interfaces were created for
                         * prefixes and dhcpv6 addresses and do not
                         * have am_ifid set.
                         */
                        if (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF ||
                            head->ipmgmt_am_linklocal) {
                                break;
                        }
                }
                if (head == NULL) {
                        err = ENOENT;
                        (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
                        break;
                }
                (void) strlcpy(aobjrval.ir_ifname, head->am_ifname,
                    sizeof (aobjrval.ir_ifname));
                aobjrval.ir_lnum = head->am_lnum;
                aobjrval.ir_family = head->am_family;
                aobjrval.ir_flags = head->am_flags;
                aobjrval.ir_atype = head->am_atype;
                aobjrval.ir_atype_cache = head->am_atype_cache;
                (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
                break;
        case IPMGMT_CMD_LIF2ADDROBJ:
                rsize = sizeof (ipmgmt_aobjop_rval_t);
                rvalp = &aobjrval;
                bzero(&aobjrval, sizeof (aobjrval));
                if (ifname[0] == '\0') {
                        err = EINVAL;
                        break;
                }
                (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock);
                head = aobjmap.aobjmap_head;
                for (; head; head = head->am_next) {
                        if (strcmp(head->am_ifname, ifname) == 0 &&
                            head->am_lnum == lnum &&
                            head->am_family == af) {
                                break;
                        }
                }
                if (head == NULL) {
                        err = ENOENT;
                        (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
                        break;
                }
                (void) strlcpy(aobjrval.ir_aobjname, head->am_aobjname,
                    sizeof (aobjrval.ir_aobjname));
                aobjrval.ir_atype = head->am_atype;
                aobjrval.ir_flags = head->am_flags;
                aobjrval.ir_atype_cache = head->am_atype_cache;
                (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
                break;
        default:
                rsize = sizeof (ipmgmt_retval_t);
                rvalp = &rval;
                err = EINVAL;
        }
        ((ipmgmt_retval_t *)rvalp)->ir_err = err;
        (void) door_return((char *)rvalp, rsize, NULL, 0);
}

/*
 * Given an interface name and family, deletes all the address objects
 * associated with it.
 */
void
i_ipmgmt_delif_aobjs(char *ifname, sa_family_t af, uint32_t flags)
{
        ipmgmt_aobjmap_t        *head, *next, *prev;
        ipadm_db_op_t           db_op;

        prev = NULL;

        (void) pthread_rwlock_wrlock(&aobjmap.aobjmap_rwlock);
        head = aobjmap.aobjmap_head;
        for (; head; head = next) {
                next = head->am_next;
                if (strcmp(head->am_ifname, ifname) != 0 ||
                    head->am_family != af) {
                        prev = head;
                        continue;
                }

                if (head->am_flags == (IPMGMT_ACTIVE|IPMGMT_PERSIST) &&
                    flags == IPMGMT_ACTIVE) {
                        /*
                         * If the addres is present in both active and
                         * persistent store, and if we are performing
                         * a temporary delete, we update the node to
                         * indicate that the address is only present in
                         * persistent store and we proceed. Otherwise
                         * we always delete the node from aobjmap.
                         */
                        head->am_flags &= ~IPMGMT_ACTIVE;
                        head->am_lnum = -1;
                        db_op = IPADM_DB_WRITE;
                } else {
                        db_op = IPADM_DB_DELETE;
                        if (prev == NULL)
                                aobjmap.aobjmap_head = next;
                        else
                                prev->am_next = next;
                }
                (void) ipmgmt_persist_aobjmap(head, db_op);
                if (db_op == IPADM_DB_DELETE)
                        free(head);
        }
        (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
}

/*
 * Handles the door command IPMGMT_CMD_SETIF. It persists the interface
 * information in the DB.
 */
static void
ipmgmt_setif_handler(void *argp)
{
        ipmgmt_retval_t         rval;

        rval.ir_err = ipmgmt_persist_if(argp);
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_RESETIF. For the given interface,
 * deletes all the persisted interface configuration. It also deletes, from
 * `aobjmap', all the address objects configured on the given interface.
 */
static void
ipmgmt_resetif_handler(void *argp)
{
        ipmgmt_if_arg_t         *rargp = argp;
        ipmgmt_retval_t         rval;
        ipmgmt_if_cbarg_t       cbarg;
        uint32_t                flags = rargp->ia_flags;
        int                     err = 0;

        cbarg.cb_family = rargp->ia_family;
        cbarg.cb_ifname = rargp->ia_ifname;

        cbarg.cb_ipv4exists = B_TRUE;
        cbarg.cb_ipv6exists = B_TRUE;

        if (flags & IPMGMT_PERSIST)
                err = ipmgmt_db_walk(ipmgmt_db_resetif, &cbarg,
                    IPADM_DB_DELETE);

        if (flags & IPMGMT_ACTIVE)
                i_ipmgmt_delif_aobjs(rargp->ia_ifname, rargp->ia_family,
                    flags);

        rval.ir_err = err;
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_RESETADDR. For the given addrobj
 * deletes all the persisted addrobj configuration. It also deletes the
 * corresponding node, from `aobjmap'.
 */
static void
ipmgmt_resetaddr_handler(void *argp)
{
        ipmgmt_addr_arg_t       *rargp = argp;
        ipmgmt_retval_t         rval;
        ipmgmt_aobjmap_t        node;
        uint32_t                flags = rargp->ia_flags;
        int                     err = 0;
        ipmgmt_resetaddr_cbarg_t cbarg;

        cbarg.cb_aobjname = rargp->ia_aobjname;

        if (flags & IPMGMT_PERSIST)
                err = ipmgmt_db_walk(ipmgmt_db_resetaddr, &cbarg,
                    IPADM_DB_DELETE);

        if (flags & IPMGMT_ACTIVE) {
                bzero(&node, sizeof (node));
                (void) strlcpy(node.am_aobjname, rargp->ia_aobjname,
                    sizeof (node.am_aobjname));

                /*
                 * am_lnum is used only for IPv6 autoconf case, since there
                 * can be multiple nodes with the same aobjname.
                 */
                node.am_lnum = rargp->ia_lnum;
                node.am_flags = flags;
                (void) ipmgmt_aobjmap_op(&node, ADDROBJ_DELETE);
        }

        rval.ir_err = err;
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_GETADDR. It retrieves the persisted
 * address for a given `gargp->ia_aobjname'. If it is not defined then it
 * retrieves all the addresses configured on `gargp->ia_ifname'. The
 * "ipadm show-addr addrobj" or "ipadm show-addr <ifname>/\*" will call this
 * handler through library.
 */
static void
ipmgmt_getaddr_handler(void *argp)
{
        ipmgmt_getaddr_arg_t    *gargp = argp;

        ipmgmt_common_handler(gargp->ia_ifname, gargp->ia_aobjname,
            ipmgmt_db_getaddr);
}

/*
 * Handles the door command IPMGMT_CMD_RESETPROP. It deletes the property line
 * from the DB.
 */
static void
ipmgmt_resetprop_handler(void *argp)
{
        ipmgmt_prop_arg_t       *pargp = argp;
        ipmgmt_retval_t         rval;

        assert(pargp->ia_cmd == IPMGMT_CMD_RESETPROP);

        rval.ir_err = ipmgmt_db_walk(ipmgmt_db_resetprop, pargp,
            IPADM_DB_DELETE);
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_GETIF. It retrieves the names of all
 * persisted interfaces and the IP protocol families (IPv4 or IPv6) they
 * support. Returns the info as a nvlist using door_return() from
 * ipmgmt_common_handler().
 */
static void
ipmgmt_getif_handler(void *argp)
{
        ipmgmt_getif_arg_t  *getif = argp;

        assert(getif->ia_cmd == IPMGMT_CMD_GETIF);

        ipmgmt_common_handler(getif->ia_ifname, NULL,
            ipmgmt_db_getif);
}

/*
 * Handles the door command IPMGMT_CMD_INITIF. It retrieves all the persisted
 * interface configuration (interface properties and addresses), for all those
 * interfaces that need to be initialized.
 */
static void
ipmgmt_initif_handler(void *argp)
{
        ipmgmt_initif_arg_t     *initif = argp;
        size_t                  buflen, nvlsize;
        char                    *buf = NULL, *onvlbuf, *invlbuf;
        ipmgmt_get_rval_t       rval, *rvalp = &rval;
        ipmgmt_initif_cbarg_t   cbarg;
        int                     err;

        assert(initif->ia_cmd == IPMGMT_CMD_INITIF);

        bzero(&cbarg, sizeof (cbarg));
        invlbuf = (char *)argp + sizeof (ipmgmt_initif_arg_t);
        nvlsize = initif->ia_nvlsize;
        err = nvlist_unpack(invlbuf, nvlsize, &cbarg.cb_invl, 0);
        if (err != 0)
                goto fail;

        cbarg.cb_family = initif->ia_family;
        if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0)
                goto fail;

        err = ipmgmt_db_walk(ipmgmt_db_initif, &cbarg, IPADM_DB_READ);
        if (err == ENOENT && cbarg.cb_ocnt > 0) {
                /*
                 * If there is at least one entry in the nvlist,
                 * do not return error.
                 */
                err = 0;
        }
        if (err != 0)
                goto fail;

        if ((err = nvlist_size(cbarg.cb_onvl, &nvlsize, NV_ENCODE_NATIVE)) != 0)
                goto fail;

        if (nvlsize > (UINT32_MAX - sizeof (ipmgmt_get_rval_t)))
                goto fail;

        buflen = nvlsize + sizeof (ipmgmt_get_rval_t);
        /*
         * We cannot use malloc() here because door_return never returns, and
         * memory allocated by malloc() would get leaked. Use alloca() instead.
         */
        buf = alloca(buflen);
        onvlbuf = buf + sizeof (ipmgmt_get_rval_t);
        if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf, &nvlsize,
            NV_ENCODE_NATIVE, 0)) != 0) {
                goto fail;
        }
        nvlist_free(cbarg.cb_invl);
        nvlist_free(cbarg.cb_onvl);
        rvalp = (ipmgmt_get_rval_t *)(void *)buf;
        rvalp->ir_err = 0;
        rvalp->ir_nvlsize = nvlsize;

        (void) door_return(buf, buflen, NULL, 0);
        return;
fail:
        nvlist_free(cbarg.cb_invl);
        nvlist_free(cbarg.cb_onvl);
        rvalp->ir_err = err;
        (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
}

int
ipmgmt_persist_if(ipmgmt_if_arg_t *sargp)
{
        ipadm_dbwrite_cbarg_t   cb;
        uint32_t        flags = sargp->ia_flags;
        nvlist_t        *nvl = NULL;
        char    strval[IPMGMT_STRSIZE];
        int     err = 0;

        if (!(flags & IPMGMT_PERSIST) || sargp->ia_family == AF_UNSPEC ||
            sargp->ia_ifname[0] == '\0') {
                err = EINVAL;
                goto ret;
        }
        if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
                goto ret;

        if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
            sargp->ia_ifname)) != 0)
                goto ret;

        if ((err = ipmgmt_update_family_nvp(nvl, sargp->ia_family,
            IPMGMT_APPEND)) != 0)
                goto ret;

        (void) snprintf(strval, IPMGMT_STRSIZE, "%d", sargp->ia_ifclass);
        if ((err = nvlist_add_string(nvl, IPADM_NVP_IFCLASS, strval)) != 0)
                goto ret;

        cb.dbw_nvl = nvl;
        cb.dbw_flags = IPMGMT_APPEND | IPMGMT_UPDATE_IF;
        err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE);
ret:
        nvlist_free(nvl);
        return (err);
}

/*
 * The helper for ipmgmt_getif_handler and ipmgmt_getaddr_handler
 */
static void
ipmgmt_common_handler(char *if_name, char *aobj_name, db_wfunc_t worker)
{
        size_t                  buflen, onvlsize;
        char                    *buf, *onvlbuf;
        ipmgmt_get_cbarg_t      cbarg;
        ipmgmt_get_rval_t       rval, *rvalp = &rval;
        int                     err = 0;

        cbarg.cb_ifname = if_name;
        cbarg.cb_aobjname = aobj_name;
        cbarg.cb_ocnt = 0;

        if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0)
                goto fail;

        err = ipmgmt_db_walk(worker, &cbarg, IPADM_DB_READ);
        if (err == ENOENT && cbarg.cb_ocnt > 0) {
                /*
                 * If there is at least one entry in the nvlist,
                 * do not return error.
                 */
                err = 0;
        }
        if (err != 0)
                goto fail;

        if ((err = nvlist_size(cbarg.cb_onvl, &onvlsize,
            NV_ENCODE_NATIVE)) != 0)
                goto fail;

        if (onvlsize > (UINT32_MAX - sizeof (ipmgmt_get_rval_t)))
                goto fail;

        buflen = onvlsize + sizeof (ipmgmt_get_rval_t);
        /*
         * We cannot use malloc() here because door_return never returns, and
         * memory allocated by malloc() would get leaked. Use alloca() instead.
         */
        buf = alloca(buflen);
        onvlbuf = buf + sizeof (ipmgmt_get_rval_t);
        if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf,
            &onvlsize, NV_ENCODE_NATIVE, 0)) != 0)
                goto fail;

        nvlist_free(cbarg.cb_onvl);
        rvalp = (ipmgmt_get_rval_t *)(void *)buf;
        rvalp->ir_err = 0;
        rvalp->ir_nvlsize = onvlsize;

        (void) door_return(buf, buflen, NULL, 0);

fail:
        nvlist_free(cbarg.cb_onvl);
        rvalp->ir_err = err;
        (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
}

/*
 * Handles the door command IPMGMT_CMD_IPMP_UPDATE
 */
static void
ipmgmt_ipmp_update_handler(void *argp)
{
        ipmgmt_ipmp_update_arg_t *uargp = argp;
        ipmgmt_retval_t rval;
        ipadm_dbwrite_cbarg_t   cb;

        boolean_t       gif_exists;
        char            gifname[LIFNAMSIZ];
        nvlist_t        *nvl = NULL;
        uint32_t        flags = uargp->ia_flags;
        int             err = 0;

        assert(uargp->ia_cmd == IPMGMT_CMD_IPMP_UPDATE);

        gif_exists = ipmgmt_persist_if_exists(uargp->ia_gifname,
            AF_UNSPEC);

        if (!ipmgmt_persist_if_exists(uargp->ia_mifname, AF_UNSPEC)) {
                err = EINVAL;
                goto ret;
        }

        ipmgmt_get_group_interface(uargp->ia_mifname, gifname, LIFNAMSIZ);

        if (flags & IPMGMT_APPEND) {
                /* Group interface should be available in the DB */
                if (!gif_exists) {
                        err = ENOENT;
                        goto ret;
                }

                if (gifname[0] != '\0') {
                        err = EEXIST;
                        goto ret;
                }
        }

        if (flags & IPMGMT_REMOVE) {
                /* We cannot remove something that does not exist */
                if (!gif_exists || gifname[0] == '\0') {
                        err = ENOENT;
                        goto ret;
                }
                if (strcmp(uargp->ia_gifname, gifname) != 0) {
                        err = EINVAL;
                        goto ret;
                }
        }

        if (flags & IPMGMT_PERSIST) {
                if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
                        goto ret;

                if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
                    uargp->ia_gifname)) != 0)
                        goto ret;

                if ((err = nvlist_add_string(nvl, IPADM_NVP_MIFNAMES,
                    uargp->ia_mifname)) != 0)
                        goto ret;

                if ((err = nvlist_add_string(nvl, IPADM_NVP_GIFNAME,
                    uargp->ia_gifname)) != 0)
                        goto ret;

                cb.dbw_nvl = nvl;
                cb.dbw_flags = flags | IPMGMT_UPDATE_IF | IPMGMT_UPDATE_IPMP;
                err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE);
        }
ret:
        nvlist_free(nvl);
        rval.ir_err = err;
        (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
}