root/usr/src/lib/libiscsit/common/libiscsit.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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <uuid/uuid.h>
#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <libintl.h>
#include <libscf.h>
#include <assert.h>

#include <libstmf.h>
#include <libiscsit.h>
#include <sys/iscsi_protocol.h>
#include <sys/iscsit/isns_protocol.h>

/* From iscsitgtd */
#define TARGET_NAME_VERS        2

/* this should be defined someplace central... */
#define ISCSI_NAME_LEN_MAX      223

/* max length of a base64 encoded secret */
#define MAX_BASE64_LEN          341

/* Default RADIUS server port */
#define DEFAULT_RADIUS_PORT     1812

/* The iscsit SMF service FMRI */
#define ISCSIT_FMRI             "svc:/network/iscsi/target:default"
/*
 * The kernel reserves target portal group tag value 1 as the default.
 */
#define ISCSIT_DEFAULT_TPGT     1
#define MAXTAG                  0xffff

/* helper for property list validation */
#define PROPERR(lst, key, value) { \
        if (lst) { \
                (void) nvlist_add_string(lst, key, value); \
        } \
}

/* helper function declarations */
static int
it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix);

static int
it_val_pass(char *name, char *val, nvlist_t *e);

/* consider making validate funcs public */
static int
it_validate_configprops(nvlist_t *nvl, nvlist_t *errs);

static int
it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs);

static int
it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs);

static boolean_t
is_iscsit_enabled(void);

static void
iqnstr(char *s);

static void
euistr(char *s);

static void
free_empty_errlist(nvlist_t **errlist);

/*
 * Function:  it_config_load()
 *
 * Allocate and create an it_config_t structure representing the
 * current iSCSI configuration.  This structure is compiled using
 * the 'provider' data returned by stmfGetProviderData().  If there
 * is no provider data associated with iscsit, the it_config_t
 * structure will be set to a default configuration.
 *
 * Parameters:
 *    cfg       A C representation of the current iSCSI configuration
 *
 * Return Values:
 *    0         Success
 *    ENOMEM    Could not allocate resources
 *    EINVAL    Invalid parameter
 */
int
it_config_load(it_config_t **cfg)
{
        int             ret = 0;
        nvlist_t        *cfg_nv = NULL;
        it_config_t     *newcfg = NULL;
        uint64_t        stmf_token = 0;

        if (!cfg) {
                return (EINVAL);
        }

        *cfg = NULL;

        ret = stmfGetProviderDataProt(ISCSIT_MODNAME, &cfg_nv,
            STMF_PORT_PROVIDER_TYPE, &stmf_token);

        if ((ret == STMF_STATUS_SUCCESS) ||
            (ret == STMF_ERROR_NOT_FOUND)) {
                /*
                 * If not initialized yet, return empty it_config_t
                 * Else, convert nvlist to struct
                 */
                ret = it_nv_to_config(cfg_nv, &newcfg);
        }

        if (ret == 0) {
                newcfg->stmf_token = stmf_token;
                *cfg = newcfg;
        }

        if (cfg_nv) {
                nvlist_free(cfg_nv);
        }

        return (ret);
}

/*
 * Function:  it_config_commit()
 *
 * Informs the iscsit service that the configuration has changed and
 * commits the new configuration to persistent store by calling
 * stmfSetProviderData.  This function can be called multiple times
 * during a configuration sequence if necessary.
 *
 * Parameters:
 *    cfg       A C representation of the current iSCSI configuration
 *
 * Return Values:
 *    0         Success
 *    ENOMEM    Could not allocate resources
 *    EINVAL    Invalid it_config_t structure
 *    TBD       ioctl() failed
 *    TBD       could not save config to STMF
 */
int
it_config_commit(it_config_t *cfg)
{
        int                     ret;
        nvlist_t                *cfgnv = NULL;
        char                    *packednv = NULL;
        int                     iscsit_fd = -1;
        size_t                  pnv_size;
        iscsit_ioc_set_config_t iop;
        it_tgt_t                *tgtp;

        if (!cfg) {
                return (EINVAL);
        }

        ret = it_config_to_nv(cfg, &cfgnv);
        if (ret == 0) {
                ret = nvlist_size(cfgnv, &pnv_size, NV_ENCODE_NATIVE);
        }

        /*
         * If the iscsit service is enabled, send the changes to the
         * kernel first.  Kernel will be the final sanity check before
         * the config is saved persistently.
         *
         * This somewhat leaves open the simultaneous-change hole
         * that STMF was trying to solve, but is a better sanity
         * check and allows for graceful handling of target renames.
         */
        if ((ret == 0) && is_iscsit_enabled()) {
                packednv = malloc(pnv_size);
                if (!packednv) {
                        ret = ENOMEM;
                } else {
                        ret = nvlist_pack(cfgnv, &packednv, &pnv_size,
                            NV_ENCODE_NATIVE, 0);
                }

                if (ret == 0) {
                        iscsit_fd = open(ISCSIT_NODE, O_RDWR|O_EXCL);
                        if (iscsit_fd != -1) {
                                iop.set_cfg_vers = ISCSIT_API_VERS0;
                                iop.set_cfg_pnvlist = packednv;
                                iop.set_cfg_pnvlist_len = pnv_size;
                                if ((ioctl(iscsit_fd, ISCSIT_IOC_SET_CONFIG,
                                    &iop)) != 0) {
                                        ret = errno;
                                }

                                (void) close(iscsit_fd);
                        } else {
                                ret = errno;
                        }
                }

                if (packednv != NULL) {
                        free(packednv);
                }
        }

        /*
         * Before saving the config persistently, remove any
         * PROP_OLD_TARGET_NAME entries.  This is only interesting to
         * the active service.
         */
        if (ret == 0) {
                boolean_t       changed = B_FALSE;

                tgtp = cfg->config_tgt_list;
                for (; tgtp != NULL; tgtp = tgtp->tgt_next) {
                        if (!tgtp->tgt_properties) {
                                continue;
                        }
                        if (nvlist_exists(tgtp->tgt_properties,
                            PROP_OLD_TARGET_NAME)) {
                                (void) nvlist_remove_all(tgtp->tgt_properties,
                                    PROP_OLD_TARGET_NAME);
                                changed = B_TRUE;
                        }
                }

                if (changed) {
                        /* rebuild the config nvlist */
                        nvlist_free(cfgnv);
                        cfgnv = NULL;
                        ret = it_config_to_nv(cfg, &cfgnv);
                }
        }

        /*
         * stmfGetProviderDataProt() checks to ensure
         * that the config data hasn't changed since we fetched it.
         *
         * The kernel now has a version we need to save persistently.
         * CLI will 'do the right thing' and warn the user if it
         * gets STMF_ERROR_PROV_DATA_STALE.  We'll try once to revert
         * the kernel to the persistently saved data, but ultimately,
         * it's up to the administrator to validate things are as they
         * want them to be.
         */
        if (ret == 0) {
                ret = stmfSetProviderDataProt(ISCSIT_MODNAME, cfgnv,
                    STMF_PORT_PROVIDER_TYPE, &(cfg->stmf_token));

                if (ret == STMF_STATUS_SUCCESS) {
                        ret = 0;
                } else if (ret == STMF_ERROR_NOMEM) {
                        ret = ENOMEM;
                } else if (ret == STMF_ERROR_PROV_DATA_STALE) {
                        int             st;
                        it_config_t     *rcfg = NULL;

                        st = it_config_load(&rcfg);
                        if (st == 0) {
                                (void) it_config_commit(rcfg);
                                it_config_free(rcfg);
                        }
                }
        }

        if (cfgnv) {
                nvlist_free(cfgnv);
        }

        return (ret);
}

/*
 * Function:  it_config_setprop()
 *
 * Validate the provided property list and set the global properties
 * for iSCSI Target.  If errlist is not NULL, returns detailed
 * errors for each property that failed.  The format for errorlist
 * is key = property, value = error string.
 *
 * Parameters:
 *
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    proplist          nvlist_t containing properties for this target.
 *    errlist           (optional)  nvlist_t of errors encountered when
 *                      validating the properties.
 *
 * Return Values:
 *    0                 Success
 *    EINVAL            Invalid property
 *
 */
int
it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist)
{
        int             ret;
        nvlist_t        *errs = NULL;
        it_portal_t     *isns = NULL;
        it_portal_t     *pnext = NULL;
        it_portal_t     *newisnslist = NULL;
        char            **arr;
        uint32_t        count;
        uint32_t        newcount;
        nvlist_t        *cprops = NULL;
        char            *val = NULL;

        if (!cfg || !proplist) {
                return (EINVAL);
        }

        if (errlist) {
                (void) nvlist_alloc(&errs, 0, 0);
                *errlist = errs;
        }

        /*
         * copy the existing properties, merge, then validate
         * the merged properties before committing them.
         */
        if (cfg->config_global_properties) {
                ret = nvlist_dup(cfg->config_global_properties, &cprops, 0);
        } else {
                ret = nvlist_alloc(&cprops, NV_UNIQUE_NAME, 0);
        }

        if (ret != 0) {
                return (ret);
        }

        ret = nvlist_merge(cprops, proplist, 0);
        if (ret != 0) {
                nvlist_free(cprops);
                return (ret);
        }

        /*
         * base64 encode the radius secret, if it's changed.
         */
        val = NULL;
        (void) nvlist_lookup_string(proplist, PROP_RADIUS_SECRET, &val);
        if (val) {
                char            bsecret[MAX_BASE64_LEN];

                ret = it_val_pass(PROP_RADIUS_SECRET, val, errs);

                if (ret == 0) {
                        (void) memset(bsecret, 0, MAX_BASE64_LEN);

                        ret = iscsi_binary_to_base64_str((uint8_t *)val,
                            strlen(val), bsecret, MAX_BASE64_LEN);

                        if (ret == 0) {
                                /* replace the value in the nvlist */
                                ret = nvlist_add_string(cprops,
                                    PROP_RADIUS_SECRET, bsecret);
                        }
                }
        }

        if (ret != 0) {
                nvlist_free(cprops);
                return (ret);
        }

        /* see if we need to remove the radius server setting */
        val = NULL;
        (void) nvlist_lookup_string(cprops, PROP_RADIUS_SERVER, &val);
        if (val && (strcasecmp(val, "none") == 0)) {
                (void) nvlist_remove_all(cprops, PROP_RADIUS_SERVER);
        }

        /* and/or remove the alias */
        val = NULL;
        (void) nvlist_lookup_string(cprops, PROP_ALIAS, &val);
        if (val && (strcasecmp(val, "none") == 0)) {
                (void) nvlist_remove_all(cprops, PROP_ALIAS);
        }

        ret = it_validate_configprops(cprops, errs);
        if (ret != 0) {
                if (cprops) {
                        nvlist_free(cprops);
                }
                return (ret);
        }

        /*
         * Update iSNS server list, if exists in provided property list.
         */
        ret = nvlist_lookup_string_array(proplist, PROP_ISNS_SERVER,
            &arr, &count);

        if (ret == 0) {
                /* special case:  if "none", remove all defined */
                if (strcasecmp(arr[0], "none") != 0) {
                        ret = it_array_to_portallist(arr, count,
                            ISNS_DEFAULT_SERVER_PORT, &newisnslist, &newcount);
                } else {
                        newisnslist = NULL;
                        newcount = 0;
                        (void) nvlist_remove_all(cprops, PROP_ISNS_SERVER);
                }

                if (ret == 0) {
                        isns = cfg->config_isns_svr_list;
                        while (isns) {
                                pnext = isns->portal_next;
                                free(isns);
                                isns = pnext;
                        }

                        cfg->config_isns_svr_list = newisnslist;
                        cfg->config_isns_svr_count = newcount;

                        /*
                         * Replace the array in the nvlist to ensure
                         * duplicates are properly removed & port numbers
                         * are added.
                         */
                        if (newcount > 0) {
                                int     i = 0;
                                char    **newarray;

                                newarray = malloc(sizeof (char *) * newcount);
                                if (newarray == NULL) {
                                        ret = ENOMEM;
                                } else {
                                        for (isns = newisnslist; isns != NULL;
                                            isns = isns->portal_next) {
                                                (void) sockaddr_to_str(
                                                    &(isns->portal_addr),
                                                    &(newarray[i++]));
                                        }
                                        (void) nvlist_add_string_array(cprops,
                                            PROP_ISNS_SERVER, newarray,
                                            newcount);

                                        for (i = 0; i < newcount; i++) {
                                                if (newarray[i]) {
                                                        free(newarray[i]);
                                                }
                                        }
                                        free(newarray);
                                }
                        }
                }
        } else if (ret == ENOENT) {
                /* not an error */
                ret = 0;
        }

        if (ret == 0) {
                /* replace the global properties list */
                nvlist_free(cfg->config_global_properties);
                cfg->config_global_properties = cprops;
        } else {
                if (cprops) {
                        nvlist_free(cprops);
                }
        }

        if (ret == 0)
                free_empty_errlist(errlist);

        return (ret);
}

/*
 * Function:  it_config_free()
 *
 * Free any resources associated with the it_config_t structure.
 *
 * Parameters:
 *    cfg       A C representation of the current iSCSI configuration
 */
void
it_config_free(it_config_t *cfg)
{
        it_config_free_cmn(cfg);
}

/*
 * Function:  it_tgt_create()
 *
 * Allocate and create an it_tgt_t structure representing a new iSCSI
 * target node.  If tgt_name is NULL, then a unique target node name will
 * be generated automatically.  Otherwise, the value of tgt_name will be
 * used as the target node name.  The new it_tgt_t structure is added to
 * the target list (cfg_tgt_list) in the configuration structure, and the
 * new target will not be instantiated until the modified configuration
 * is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tgt               Pointer to an iSCSI target structure
 *    tgt_name          The target node name for the target to be created.
 *                      The name must be in either IQN or EUI format.  If
 *                      this value is NULL, a node name will be generated
 *                      automatically in IQN format.
 *
 * Return Values:
 *    0                 Success
 *    ENOMEM            Could not allocated resources
 *    EINVAL            Invalid parameter
 *    EFAULT            Invalid iSCSI name specified
 *    E2BIG             Too many already exist
 */
int
it_tgt_create(it_config_t *cfg, it_tgt_t **tgt, char *tgt_name)
{
        int             ret = 0;
        it_tgt_t        *ptr;
        it_tgt_t        *cfgtgt;
        char            *namep;
        char            buf[ISCSI_NAME_LEN_MAX + 1];

        if (!cfg || !tgt) {
                return (EINVAL);
        }

        if (!tgt_name) {
                /* generate a name */
                ret = it_iqn_generate(buf, sizeof (buf), NULL);
                if (ret != 0) {
                        return (ret);
                }
        } else {
                /* validate the passed-in name */
                if (!validate_iscsi_name(tgt_name)) {
                        return (EFAULT);
                }
                (void) strlcpy(buf, tgt_name, sizeof (buf));
                canonical_iscsi_name(buf);
        }
        namep = buf;

        /* make sure this name isn't already on the list */
        cfgtgt = cfg->config_tgt_list;
        while (cfgtgt != NULL) {
                if (strcasecmp(namep, cfgtgt->tgt_name) == 0) {
                        return (EEXIST);
                }
                cfgtgt = cfgtgt->tgt_next;
        }

        /* Too many targets? */
        if (cfg->config_tgt_count >= MAX_TARGETS) {
                return (E2BIG);
        }

        ptr = calloc(1, sizeof (it_tgt_t));
        if (ptr == NULL) {
                return (ENOMEM);
        }

        (void) strlcpy(ptr->tgt_name, namep, sizeof (ptr->tgt_name));
        ptr->tgt_generation = 1;
        ptr->tgt_next = cfg->config_tgt_list;
        cfg->config_tgt_list = ptr;
        cfg->config_tgt_count++;

        *tgt = ptr;

        return (0);
}

/*
 * Function:  it_tgt_setprop()
 *
 * Validate the provided property list and set the properties for
 * the specified target.  If errlist is not NULL, returns detailed
 * errors for each property that failed.  The format for errorlist
 * is key = property, value = error string.
 *
 * Parameters:
 *
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tgt               Pointer to an iSCSI target structure
 *    proplist          nvlist_t containing properties for this target.
 *    errlist           (optional)  nvlist_t of errors encountered when
 *                      validating the properties.
 *
 * Return Values:
 *    0                 Success
 *    EINVAL            Invalid property
 *
 */
int
it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist,
    nvlist_t **errlist)
{
        int             ret;
        nvlist_t        *errs = NULL;
        nvlist_t        *tprops = NULL;
        char            *val = NULL;

        if (!cfg || !tgt || !proplist) {
                return (EINVAL);
        }

        /* verify the target name in case the target node is renamed */
        if (!validate_iscsi_name(tgt->tgt_name)) {
                return (EINVAL);
        }
        canonical_iscsi_name(tgt->tgt_name);

        if (errlist) {
                (void) nvlist_alloc(&errs, 0, 0);
                *errlist = errs;
        }

        /*
         * copy the existing properties, merge, then validate
         * the merged properties before committing them.
         */
        if (tgt->tgt_properties) {
                ret = nvlist_dup(tgt->tgt_properties, &tprops, 0);
        } else {
                ret = nvlist_alloc(&tprops, NV_UNIQUE_NAME, 0);
        }

        if (ret != 0) {
                return (ret);
        }

        ret = nvlist_merge(tprops, proplist, 0);
        if (ret != 0) {
                nvlist_free(tprops);
                return (ret);
        }

        /* unset chap username or alias if requested */
        val = NULL;
        (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_USER, &val);
        if (val && (strcasecmp(val, "none") == 0)) {
                (void) nvlist_remove_all(tprops, PROP_TARGET_CHAP_USER);
        }

        val = NULL;
        (void) nvlist_lookup_string(proplist, PROP_ALIAS, &val);
        if (val && (strcasecmp(val, "none") == 0)) {
                (void) nvlist_remove_all(tprops, PROP_ALIAS);
        }

        /* base64 encode the CHAP secret, if it's changed */
        val = NULL;
        (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_SECRET, &val);
        if (val) {
                char            bsecret[MAX_BASE64_LEN];

                ret = it_val_pass(PROP_TARGET_CHAP_SECRET, val, errs);

                if (ret == 0) {
                        (void) memset(bsecret, 0, MAX_BASE64_LEN);

                        ret = iscsi_binary_to_base64_str((uint8_t *)val,
                            strlen(val), bsecret, MAX_BASE64_LEN);

                        if (ret == 0) {
                                /* replace the value in the nvlist */
                                ret = nvlist_add_string(tprops,
                                    PROP_TARGET_CHAP_SECRET, bsecret);
                        }
                }
        }

        if (ret == 0) {
                ret = it_validate_tgtprops(tprops, errs);
        }

        if (ret != 0) {
                if (tprops) {
                        nvlist_free(tprops);
                }
                return (ret);
        }

        if (tgt->tgt_properties) {
                nvlist_free(tgt->tgt_properties);
        }
        tgt->tgt_properties = tprops;

        free_empty_errlist(errlist);

        return (0);
}


/*
 * Function:  it_tgt_delete()
 *
 * Delete target represented by 'tgt', where 'tgt' is an existing
 * it_tgt_structure within the configuration 'cfg'.  The target removal
 * will not take effect until the modified configuration is committed
 * by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tgt               Pointer to an iSCSI target structure
 *
 *    force             Set the target to offline before removing it from
 *                      the config.  If not specified, the operation will
 *                      fail if the target is determined to be online.
 * Return Values:
 *    0                 Success
 *    EBUSY             Target is online
 */
int
it_tgt_delete(it_config_t *cfg, it_tgt_t *tgt, boolean_t force)
{
        int                     ret;
        it_tgt_t                *ptgt;
        it_tgt_t                *prev = NULL;
        stmfDevid               devid;
        stmfTargetProperties    props;

        if (!cfg || !tgt) {
                return (0);
        }

        ptgt = cfg->config_tgt_list;
        while (ptgt != NULL) {
                if (strcasecmp(tgt->tgt_name, ptgt->tgt_name) == 0) {
                        break;
                }
                prev = ptgt;
                ptgt = ptgt->tgt_next;
        }

        if (!ptgt) {
                return (0);
        }

        /*
         * check to see if this target is offline.  If it is not,
         * and the 'force' flag is TRUE, tell STMF to offline it
         * before removing from the configuration.
         */
        ret = stmfDevidFromIscsiName(ptgt->tgt_name, &devid);
        if (ret != STMF_STATUS_SUCCESS) {
                /* can't happen? */
                return (EINVAL);
        }

        ret = stmfGetTargetProperties(&devid, &props);
        if (ret == STMF_STATUS_SUCCESS) {
                /*
                 * only other return is STMF_ERROR_NOT_FOUND, which
                 * means we don't have to offline it.
                 */
                if (props.status == STMF_TARGET_PORT_ONLINE) {
                        if (!force) {
                                return (EBUSY);
                        }
                        ret = stmfOfflineTarget(&devid);
                        if (ret != 0) {
                                return (EBUSY);
                        }
                }
        }

        if (prev) {
                prev->tgt_next = ptgt->tgt_next;
        } else {
                /* first one on the list */
                cfg->config_tgt_list = ptgt->tgt_next;
        }

        ptgt->tgt_next = NULL; /* Only free this target */

        cfg->config_tgt_count--;
        it_tgt_free(ptgt);

        return (0);
}

/*
 * Function:  it_tgt_free()
 *
 * Frees an it_tgt_t structure.  If tgt_next is not NULL, frees
 * all structures in the list.
 */
void
it_tgt_free(it_tgt_t *tgt)
{
        it_tgt_free_cmn(tgt);
}

/*
 * Function:  it_tpgt_create()
 *
 * Allocate and create an it_tpgt_t structure representing a new iSCSI
 * target portal group tag.  The new it_tpgt_t structure is added to the
 * target tpgt list (tgt_tpgt_list) in the it_tgt_t structure.  The new
 * target portal group tag will not be instantiated until the modified
 * configuration is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tgt               Pointer to the iSCSI target structure associated
 *                      with the target portal group tag
 *    tpgt              Pointer to a target portal group tag structure
 *    tpg_name          The name of the TPG to be associated with this TPGT
 *    tpgt_tag          16-bit numerical identifier for this TPGT.  If
 *                      tpgt_tag is '0', this function will choose the
 *                      tag number.  If tpgt_tag is >0, and the requested
 *                      tag is determined to be in use, another value
 *                      will be chosen.
 *
 * Return Values:
 *    0                 Success
 *    ENOMEM            Could not allocate resources
 *    EINVAL            Invalid parameter
 *    EEXIST            Specified tag name is already used.
 *    E2BIG             No available tag numbers
 */
int
it_tpgt_create(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t **tpgt,
    char *tpg_name, uint16_t tpgt_tag)
{
        it_tpgt_t       *ptr = NULL;
        it_tpgt_t       *cfgt;
        char            tagid_used[MAXTAG + 1];
        uint16_t        tagid = ISCSIT_DEFAULT_TPGT;

        if (!cfg || !tgt || !tpgt || !tpg_name) {
                return (EINVAL);
        }

        (void) memset(&(tagid_used[0]), 0, sizeof (tagid_used));

        /*
         * Make sure this name and/or tag isn't already on the list
         * At the same time, capture all tag ids in use for this target
         *
         * About tag numbering -- since tag numbers are used by
         * the iSCSI protocol, we should be careful about reusing
         * them too quickly.  Start with a value greater than the
         * highest one currently defined.  If current == MAXTAG,
         * just find an unused tag.
         */
        cfgt = tgt->tgt_tpgt_list;
        while (cfgt != NULL) {
                tagid_used[cfgt->tpgt_tag] = 1;

                if (strcmp(tpg_name, cfgt->tpgt_tpg_name) == 0) {
                        return (EEXIST);
                }

                if (cfgt->tpgt_tag > tagid) {
                        tagid = cfgt->tpgt_tag;
                }

                cfgt = cfgt->tpgt_next;
        }

        if ((tpgt_tag > ISCSIT_DEFAULT_TPGT) && (tpgt_tag < MAXTAG) &&
            (tagid_used[tpgt_tag] == 0)) {
                /* ok to use requested */
                tagid = tpgt_tag;
        } else if (tagid == MAXTAG) {
                /*
                 * The highest value is used, find an available id.
                 */
                tagid = ISCSIT_DEFAULT_TPGT + 1;
                for (; tagid < MAXTAG; tagid++) {
                        if (tagid_used[tagid] == 0) {
                                break;
                        }
                }
                if (tagid >= MAXTAG) {
                        return (E2BIG);
                }
        } else {
                /* next available ID */
                tagid++;
        }

        ptr = calloc(1, sizeof (it_tpgt_t));
        if (!ptr) {
                return (ENOMEM);
        }

        (void) strlcpy(ptr->tpgt_tpg_name, tpg_name,
            sizeof (ptr->tpgt_tpg_name));
        ptr->tpgt_generation = 1;
        ptr->tpgt_tag = tagid;

        ptr->tpgt_next = tgt->tgt_tpgt_list;
        tgt->tgt_tpgt_list = ptr;
        tgt->tgt_tpgt_count++;
        tgt->tgt_generation++;

        *tpgt = ptr;

        return (0);
}

/*
 * Function:  it_tpgt_delete()
 *
 * Delete the target portal group tag represented by 'tpgt', where
 * 'tpgt' is an existing is_tpgt_t structure within the target 'tgt'.
 * The target portal group tag removal will not take effect until the
 * modified configuration is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tgt               Pointer to the iSCSI target structure associated
 *                      with the target portal group tag
 *    tpgt              Pointer to a target portal group tag structure
 */
void
it_tpgt_delete(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t *tpgt)
{
        it_tpgt_t       *ptr;
        it_tpgt_t       *prev = NULL;

        if (!cfg || !tgt || !tpgt) {
                return;
        }

        ptr = tgt->tgt_tpgt_list;
        while (ptr) {
                if (ptr->tpgt_tag == tpgt->tpgt_tag) {
                        break;
                }
                prev = ptr;
                ptr = ptr->tpgt_next;
        }

        if (!ptr) {
                return;
        }

        if (prev) {
                prev->tpgt_next = ptr->tpgt_next;
        } else {
                tgt->tgt_tpgt_list = ptr->tpgt_next;
        }
        ptr->tpgt_next = NULL;

        tgt->tgt_tpgt_count--;
        tgt->tgt_generation++;

        it_tpgt_free(ptr);
}

/*
 * Function:  it_tpgt_free()
 *
 * Deallocates resources of an it_tpgt_t structure.  If tpgt->next
 * is not NULL, frees all members of the list.
 */
void
it_tpgt_free(it_tpgt_t *tpgt)
{
        it_tpgt_free_cmn(tpgt);
}

/*
 * Function:  it_tpg_create()
 *
 * Allocate and create an it_tpg_t structure representing a new iSCSI
 * target portal group.  The new it_tpg_t structure is added to the global
 * tpg list (cfg_tgt_list) in the it_config_t structure.  The new target
 * portal group will not be instantiated until the modified configuration
 * is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tpg               Pointer to the it_tpg_t structure representing
 *                      the target portal group
 *    tpg_name          Identifier for the target portal group
 *    portal_ip_port    A string containing an appropriatedly formatted
 *                      IP address:port.  Both IPv4 and IPv6 addresses are
 *                      permitted.  This value becomes the first portal in
 *                      the TPG -- applications can add additional values
 *                      using it_portal_create() before committing the TPG.
 * Return Values:
 *    0                 Success
 *    ENOMEM            Cannot allocate resources
 *    EINVAL            Invalid parameter
 *    EEXIST            Requested portal in use by another target portal
 *                      group
 */
int
it_tpg_create(it_config_t *cfg, it_tpg_t **tpg, char *tpg_name,
    char *portal_ip_port)
{
        int             ret;
        it_tpg_t        *ptr;
        it_portal_t     *portal = NULL;

        if (!cfg || !tpg || !tpg_name || !portal_ip_port) {
                return (EINVAL);
        }

        *tpg = NULL;

        ptr = cfg->config_tpg_list;
        while (ptr) {
                if (strcmp(tpg_name, ptr->tpg_name) == 0) {
                        break;
                }
                ptr = ptr->tpg_next;
        }

        if (ptr) {
                return (EEXIST);
        }

        ptr = calloc(1, sizeof (it_tpg_t));
        if (!ptr) {
                return (ENOMEM);
        }

        ptr->tpg_generation = 1;
        (void) strlcpy(ptr->tpg_name, tpg_name, sizeof (ptr->tpg_name));

        /* create the portal */
        ret = it_portal_create(cfg, ptr, &portal, portal_ip_port);
        if (ret != 0) {
                free(ptr);
                return (ret);
        }

        ptr->tpg_next = cfg->config_tpg_list;
        cfg->config_tpg_list = ptr;
        cfg->config_tpg_count++;

        *tpg = ptr;

        return (0);
}

/*
 * Function:  it_tpg_delete()
 *
 * Delete target portal group represented by 'tpg', where 'tpg' is an
 * existing it_tpg_t structure within the global configuration 'cfg'.
 * The target portal group removal will not take effect until the
 * modified configuration is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configuration obtained from
 *                      it_config_load()
 *    tpg               Pointer to the it_tpg_t structure representing
 *                      the target portal group
 *    force             Remove this target portal group even if it's
 *                      associated with one or more targets.
 *
 * Return Values:
 *    0                 Success
 *    EINVAL            Invalid parameter
 *    EBUSY             Portal group associated with one or more targets.
 */
int
it_tpg_delete(it_config_t *cfg, it_tpg_t *tpg, boolean_t force)
{
        it_tpg_t        *ptr;
        it_tpg_t        *prev = NULL;
        it_tgt_t        *tgt;
        it_tpgt_t       *tpgt;
        it_tpgt_t       *ntpgt;

        if (!cfg || !tpg) {
                return (EINVAL);
        }

        ptr = cfg->config_tpg_list;
        while (ptr) {
                if (strcmp(ptr->tpg_name, tpg->tpg_name) == 0) {
                        break;
                }
                prev = ptr;
                ptr = ptr->tpg_next;
        }

        if (!ptr) {
                return (0);
        }

        /*
         * See if any targets are using this portal group.
         * If there are, and the force flag is not set, fail.
         */
        tgt = cfg->config_tgt_list;
        while (tgt) {
                tpgt = tgt->tgt_tpgt_list;
                while (tpgt) {
                        ntpgt = tpgt->tpgt_next;

                        if (strcmp(tpgt->tpgt_tpg_name, tpg->tpg_name)
                            == 0) {
                                if (!force) {
                                        return (EBUSY);
                                }
                                it_tpgt_delete(cfg, tgt, tpgt);
                        }

                        tpgt = ntpgt;
                }
                tgt = tgt->tgt_next;
        }

        /* Now that it's not in use anywhere, remove the TPG */
        if (prev) {
                prev->tpg_next = ptr->tpg_next;
        } else {
                cfg->config_tpg_list = ptr->tpg_next;
        }
        ptr->tpg_next = NULL;

        cfg->config_tpg_count--;

        it_tpg_free(ptr);

        return (0);
}

/*
 * Function:  it_tpg_free()
 *
 * Deallocates resources associated with an it_tpg_t structure.
 * If tpg->next is not NULL, frees all members of the list.
 */
void
it_tpg_free(it_tpg_t *tpg)
{
        it_tpg_free_cmn(tpg);
}

/*
 * Function:  it_portal_create()
 *
 * Add an it_portal_t structure presenting a new portal to the specified
 * target portal group.  The change to the target portal group will not take
 * effect until the modified configuration is committed by calling
 * it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configration obtained from
 *                      it_config_load()
 *    tpg               Pointer to the it_tpg_t structure representing the
 *                      target portal group
 *    portal            Pointer to the it_portal_t structure representing
 *                      the portal
 *    portal_ip_port    A string containing an appropriately formatted
 *                      IP address or IP address:port in either IPv4 or
 *                      IPv6 format.
 * Return Values:
 *    0                 Success
 *    ENOMEM            Could not allocate resources
 *    EINVAL            Invalid parameter
 *    EEXIST            Portal already configured for another portal group
 */
int
it_portal_create(it_config_t *cfg, it_tpg_t *tpg, it_portal_t **portal,
    char *portal_ip_port)
{
        struct sockaddr_storage         sa;
        it_portal_t                     *ptr;
        it_tpg_t                        *ctpg = NULL;

        if (!cfg || !tpg || !portal || !portal_ip_port) {
                return (EINVAL);
        }

        if ((it_common_convert_sa(portal_ip_port, &sa, ISCSI_LISTEN_PORT))
            == NULL) {
                return (EINVAL);
        }

        /* Check that this portal doesn't appear in any other tag */
        ctpg = cfg->config_tpg_list;
        while (ctpg) {
                ptr = ctpg->tpg_portal_list;
                for (; ptr != NULL; ptr = ptr->portal_next) {
                        if (it_sa_compare(&(ptr->portal_addr), &sa) != 0) {
                                continue;
                        }

                        /*
                         * Existing in the same group is not an error,
                         * but don't add it again.
                         */
                        if (strcmp(ctpg->tpg_name, tpg->tpg_name) == 0) {
                                return (0);
                        } else {
                                /* Not allowed */
                                return (EEXIST);
                        }
                }
                ctpg = ctpg->tpg_next;
        }

        ptr = calloc(1, sizeof (it_portal_t));
        if (!ptr) {
                return (ENOMEM);
        }

        (void) memcpy(&(ptr->portal_addr), &sa,
            sizeof (struct sockaddr_storage));
        ptr->portal_next = tpg->tpg_portal_list;
        tpg->tpg_portal_list = ptr;
        tpg->tpg_portal_count++;
        tpg->tpg_generation++;

        return (0);
}

/*
 * Function:  it_portal_delete()
 *
 * Remove the specified portal from the specified target portal group.
 * The portal removal will not take effect until the modified configuration
 * is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configration obtained from
 *                      it_config_load()
 *    tpg               Pointer to the it_tpg_t structure representing the
 *                      target portal group
 *    portal            Pointer to the it_portal_t structure representing
 *                      the portal
 */
void
it_portal_delete(it_config_t *cfg, it_tpg_t *tpg, it_portal_t *portal)
{
        it_portal_t     *ptr;
        it_portal_t     *prev = NULL;

        if (!cfg || !tpg || !portal) {
                return;
        }

        ptr = tpg->tpg_portal_list;
        while (ptr) {
                if (memcmp(&(ptr->portal_addr), &(portal->portal_addr),
                    sizeof (ptr->portal_addr)) == 0) {
                        break;
                }
                prev = ptr;
                ptr = ptr->portal_next;
        }

        if (!ptr) {
                return;
        }

        if (prev) {
                prev->portal_next = ptr->portal_next;
        } else {
                tpg->tpg_portal_list = ptr->portal_next;
        }
        tpg->tpg_portal_count--;
        tpg->tpg_generation++;

        free(ptr);
}

/*
 * Function:  it_ini_create()
 *
 * Add an initiator context to the global configuration. The new
 * initiator context will not be instantiated until the modified
 * configuration is committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configration obtained from
 *                      it_config_load()
 *    ini               Pointer to the it_ini_t structure representing
 *                      the initiator context.
 *    ini_node_name     The iSCSI node name of the remote initiator.
 *
 * Return Values:
 *    0                 Success
 *    ENOMEM            Could not allocate resources
 *    EINVAL            Invalid parameter.
 *    EFAULT            Invalid initiator name
 */
int
it_ini_create(it_config_t *cfg, it_ini_t **ini, char *ini_node_name)
{
        it_ini_t        *ptr;

        if (!cfg || !ini || !ini_node_name) {
                return (EINVAL);
        }

        /*
         * Ensure this is a valid ini name
         */
        if (!validate_iscsi_name(ini_node_name)) {
                return (EFAULT);
        }

        ptr = cfg->config_ini_list;
        while (ptr) {
                if (strcasecmp(ptr->ini_name, ini_node_name) == 0) {
                        break;
                }
                ptr = ptr->ini_next;
        }

        if (ptr) {
                return (EEXIST);
        }

        ptr = calloc(1, sizeof (it_ini_t));
        if (!ptr) {
                return (ENOMEM);
        }

        (void) strlcpy(ptr->ini_name, ini_node_name, sizeof (ptr->ini_name));
        ptr->ini_generation = 1;
        /* nvlist for props? */

        ptr->ini_next = cfg->config_ini_list;
        cfg->config_ini_list = ptr;
        cfg->config_ini_count++;

        *ini = ptr;

        return (0);
}

/*
 * Function:  it_ini_setprop()
 *
 * Validate the provided property list and set the initiator properties.
 * If errlist is not NULL, returns detailed errors for each property
 * that failed.  The format for errorlist is key = property,
 * value = error string.
 *
 * Parameters:
 *
 *    ini               The initiator being updated.
 *    proplist          nvlist_t containing properties for this target.
 *    errlist           (optional)  nvlist_t of errors encountered when
 *                      validating the properties.
 *
 * Return Values:
 *    0                 Success
 *    EINVAL            Invalid property
 *
 */
int
it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist)
{
        int             ret;
        nvlist_t        *errs = NULL;
        nvlist_t        *iprops = NULL;
        char            *val = NULL;

        if (!ini || !proplist) {
                return (EINVAL);
        }

        if (errlist) {
                (void) nvlist_alloc(&errs, 0, 0);
                *errlist = errs;
        }

        /*
         * copy the existing properties, merge, then validate
         * the merged properties before committing them.
         */
        if (ini->ini_properties) {
                ret = nvlist_dup(ini->ini_properties, &iprops, 0);
        } else {
                ret = nvlist_alloc(&iprops, NV_UNIQUE_NAME, 0);
        }

        if (ret != 0) {
                return (ret);
        }

        ret = nvlist_merge(iprops, proplist, 0);
        if (ret != 0) {
                nvlist_free(iprops);
                return (ret);
        }

        /* unset chap username if requested */
        if ((nvlist_lookup_string(proplist, PROP_CHAP_USER, &val)) == 0) {
                if (strcasecmp(val, "none") == 0) {
                        (void) nvlist_remove_all(iprops, PROP_CHAP_USER);
                }
        }

        /* base64 encode the CHAP secret, if it's changed */
        if ((nvlist_lookup_string(proplist, PROP_CHAP_SECRET, &val)) == 0) {
                char            bsecret[MAX_BASE64_LEN];

                ret = it_val_pass(PROP_CHAP_SECRET, val, errs);
                if (ret == 0) {
                        (void) memset(bsecret, 0, MAX_BASE64_LEN);

                        ret = iscsi_binary_to_base64_str((uint8_t *)val,
                            strlen(val), bsecret, MAX_BASE64_LEN);

                        if (ret == 0) {
                                /* replace the value in the nvlist */
                                ret = nvlist_add_string(iprops,
                                    PROP_CHAP_SECRET, bsecret);
                        }
                }
        }

        if (ret == 0) {
                ret = it_validate_iniprops(iprops, errs);
        }

        if (ret != 0) {
                if (iprops) {
                        nvlist_free(iprops);
                }
                return (ret);
        }

        if (ini->ini_properties) {
                nvlist_free(ini->ini_properties);
        }
        ini->ini_properties = iprops;

        free_empty_errlist(errlist);

        return (0);
}

/*
 * Function:  it_ini_delete()
 *
 * Remove the specified initiator context from the global configuration.
 * The removal will not take effect until the modified configuration is
 * committed by calling it_config_commit().
 *
 * Parameters:
 *    cfg               The current iSCSI configration obtained from
 *                      it_config_load()
 *    ini               Pointer to the it_ini_t structure representing
 *                      the initiator context.
 */
void
it_ini_delete(it_config_t *cfg, it_ini_t *ini)
{
        it_ini_t        *ptr;
        it_ini_t        *prev = NULL;

        if (!cfg || !ini) {
                return;
        }

        ptr = cfg->config_ini_list;
        while (ptr) {
                if (strcasecmp(ptr->ini_name, ini->ini_name) == 0) {
                        break;
                }
                prev = ptr;
                ptr = ptr->ini_next;
        }

        if (!ptr) {
                return;
        }

        if (prev) {
                prev->ini_next = ptr->ini_next;
        } else {
                cfg->config_ini_list = ptr->ini_next;
        }

        ptr->ini_next = NULL; /* Only free this initiator */

        cfg->config_ini_count--;

        it_ini_free(ptr);
}

/*
 * Function:  it_ini_free()
 *
 * Deallocates resources of an it_ini_t structure. If ini->next is
 * not NULL, frees all members of the list.
 */
void
it_ini_free(it_ini_t *ini)
{
        it_ini_free_cmn(ini);
}

/*
 * Goes through the target property list and validates
 * each entry.  If errs is non-NULL, will return explicit errors
 * for each property that fails validation.
 */
static int
it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs)
{
        int             errcnt = 0;
        nvpair_t        *nvp = NULL;
        data_type_t     nvtype;
        char            *name;
        char            *val;
        char            *auth = NULL;

        if (!nvl) {
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);
                nvtype = nvpair_type(nvp);

                if (!name) {
                        continue;
                }

                val = NULL;
                if (strcmp(name, PROP_TARGET_CHAP_USER) == 0) {
                        if (nvtype != DATA_TYPE_STRING) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else if (strcmp(name, PROP_TARGET_CHAP_SECRET) == 0) {
                        /*
                         * must be between 12 and 255 chars in cleartext.
                         * will be base64 encoded when it's set.
                         */
                        if (nvtype == DATA_TYPE_STRING) {
                                (void) nvpair_value_string(nvp, &val);
                        }

                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else if (strcmp(name, PROP_ALIAS) == 0) {
                        if (nvtype != DATA_TYPE_STRING) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else if (strcmp(name, PROP_AUTH) == 0) {
                        if (nvtype == DATA_TYPE_STRING) {
                                val = NULL;
                                (void) nvpair_value_string(nvp, &val);
                        }

                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                        if ((strcmp(val, PA_AUTH_NONE) != 0) &&
                            (strcmp(val, PA_AUTH_CHAP) != 0) &&
                            (strcmp(val, PA_AUTH_RADIUS) != 0) &&
                            (strcmp(val, "default") != 0)) {
                                PROPERR(errs, val, gettext(
                                    "must be none, chap, radius or default"));
                                errcnt++;
                        }
                        auth = val;
                        continue;
                } else if (strcmp(name, PROP_OLD_TARGET_NAME) == 0) {
                        continue;
                } else {
                        /* unrecognized property */
                        PROPERR(errs, name, gettext("unrecognized property"));
                        errcnt++;
                }
        }

        if (errcnt) {
                return (EINVAL);
        }

        /* if auth is being set to default, remove from this nvlist */
        if (auth && (strcmp(auth, "default") == 0)) {
                (void) nvlist_remove_all(nvl, PROP_AUTH);
        }

        return (0);
}

/*
 * Goes through the config property list and validates
 * each entry.  If errs is non-NULL, will return explicit errors
 * for each property that fails validation.
 */
static int
it_validate_configprops(nvlist_t *nvl, nvlist_t *errs)
{
        int                             errcnt = 0;
        nvpair_t                        *nvp = NULL;
        data_type_t                     nvtype;
        char                            *name;
        char                            *val;
        struct sockaddr_storage         sa;
        boolean_t                       update_rad_server = B_FALSE;
        char                            *rad_server;
        char                            *auth = NULL;

        if (!nvl) {
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);
                nvtype = nvpair_type(nvp);

                if (!name) {
                        continue;
                }

                val = NULL;

                /* prefetch string value as we mostly need it */
                if (nvtype == DATA_TYPE_STRING) {
                        (void) nvpair_value_string(nvp, &val);
                }

                if (strcmp(name, PROP_ALIAS) == 0) {
                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                        }
                } else if (strcmp(name, PROP_AUTH) == 0) {
                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }

                        if ((strcmp(val, PA_AUTH_NONE) != 0) &&
                            (strcmp(val, PA_AUTH_CHAP) != 0) &&
                            (strcmp(val, PA_AUTH_RADIUS) != 0)) {
                                PROPERR(errs, PROP_AUTH,
                                    gettext("must be none, chap or radius"));
                                errcnt++;
                        }

                        auth = val;

                } else if (strcmp(name, PROP_ISNS_ENABLED) == 0) {
                        if (nvtype != DATA_TYPE_BOOLEAN_VALUE) {
                                PROPERR(errs, name,
                                    gettext("must be a boolean value"));
                                errcnt++;
                        }
                } else if (strcmp(name, PROP_ISNS_SERVER) == 0) {
                        char            **arr = NULL;
                        uint32_t        acount = 0;

                        (void) nvlist_lookup_string_array(nvl, name,
                            &arr, &acount);

                        while (acount > 0) {
                                if (strcasecmp(arr[acount - 1], "none") == 0) {
                                        break;
                                }
                                if ((it_common_convert_sa(arr[acount - 1],
                                    &sa, 0)) == NULL) {
                                        PROPERR(errs, arr[acount - 1],
                                            gettext("invalid address"));
                                        errcnt++;
                                }
                                acount--;
                        }

                } else if (strcmp(name, PROP_RADIUS_SECRET) == 0) {
                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else if (strcmp(name, PROP_RADIUS_SERVER) == 0) {
                        struct sockaddr_storage         sa;
                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }

                        if ((it_common_convert_sa(val, &sa,
                            DEFAULT_RADIUS_PORT)) == NULL) {
                                PROPERR(errs, name,
                                    gettext("invalid address"));
                                errcnt++;
                        } else {
                                /*
                                 * rewrite this property to ensure port
                                 * number is added.
                                 */

                                if (sockaddr_to_str(&sa, &rad_server) == 0) {
                                        update_rad_server = B_TRUE;
                                }
                        }
                } else {
                        /* unrecognized property */
                        PROPERR(errs, name, gettext("unrecognized property"));
                        errcnt++;
                }
        }

        /*
         * If we successfully reformatted the radius server to add the port
         * number then update the nvlist
         */
        if (update_rad_server) {
                (void) nvlist_add_string(nvl, PROP_RADIUS_SERVER, rad_server);
                free(rad_server);
        }

        /*
         * if auth = radius, ensure radius server & secret are set.
         */
        if (auth) {
                if (strcmp(auth, PA_AUTH_RADIUS) == 0) {
                        /* need server & secret for radius */
                        if (!nvlist_exists(nvl, PROP_RADIUS_SERVER)) {
                                PROPERR(errs, PROP_RADIUS_SERVER,
                                    gettext("missing required property"));
                                errcnt++;
                        }
                        if (!nvlist_exists(nvl, PROP_RADIUS_SECRET)) {
                                PROPERR(errs, PROP_RADIUS_SECRET,
                                    gettext("missing required property"));
                                errcnt++;
                        }
                }
        }

        if (errcnt) {
                return (EINVAL);
        }

        return (0);
}

/*
 * Goes through the ini property list and validates
 * each entry.  If errs is non-NULL, will return explicit errors
 * for each property that fails validation.
 */
static int
it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs)
{
        int                             errcnt = 0;
        nvpair_t                        *nvp = NULL;
        data_type_t                     nvtype;
        char                            *name;
        char                            *val;

        if (!nvl) {
                return (0);
        }

        while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
                name = nvpair_name(nvp);
                nvtype = nvpair_type(nvp);

                if (!name) {
                        continue;
                }

                if (strcmp(name, PROP_CHAP_USER) == 0) {
                        if (nvtype != DATA_TYPE_STRING) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else if (strcmp(name, PROP_CHAP_SECRET) == 0) {
                        /*
                         * must be between 12 and 255 chars in cleartext.
                         * will be base64 encoded when it's set.
                         */
                        if (nvtype == DATA_TYPE_STRING) {
                                val = NULL;
                                (void) nvpair_value_string(nvp, &val);
                        }

                        if (!val) {
                                PROPERR(errs, name,
                                    gettext("must be a string value"));
                                errcnt++;
                                continue;
                        }
                } else {
                        /* unrecognized property */
                        PROPERR(errs, name, gettext("unrecognized property"));
                        errcnt++;
                }
        }

        if (errcnt) {
                return (EINVAL);
        }

        return (0);
}

static int
it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix)
{
        int             ret;
        uuid_t          id;
        char            id_str[UUID_PRINTABLE_STRING_LENGTH];

        uuid_generate_random(id);
        uuid_unparse(id, id_str);

        if (opt_iqn_suffix) {
                ret = snprintf(iqn_buf, iqn_buf_len, DEFAULT_IQN
                    "%02d:%s.%s", TARGET_NAME_VERS, id_str, opt_iqn_suffix);
        } else {
                ret = snprintf(iqn_buf, iqn_buf_len, DEFAULT_IQN
                    "%02d:%s", TARGET_NAME_VERS, id_str);
        }

        if (ret > iqn_buf_len) {
                return (1);
        }

        return (0);
}

static int
it_val_pass(char *name, char *val, nvlist_t *e)
{
        size_t          sz;

        if (!name || !val) {
                return (EINVAL);
        }

        /*
         * must be at least 12 chars and less than 256 chars cleartext.
         */
        sz = strlen(val);

        /*
         * Since we will be automatically encoding secrets we don't really
         * need the prefix anymore.
         */
        if (sz < 12) {
                PROPERR(e, name, gettext("secret too short"));
        } else if (sz > 255) {
                PROPERR(e, name, gettext("secret too long"));
        } else {
                /* all is well */
                return (0);
        }

        return (1);
}

/*
 * Function:  validate_iscsi_name()
 *
 * Ensures the passed-in string is a valid IQN or EUI iSCSI name
 *
 */
boolean_t
validate_iscsi_name(char *in_name)
{
        size_t          in_len;
        int             i;
        char            month[3];

        if (in_name == NULL) {
                return (B_FALSE);
        }

        in_len = strlen(in_name);
        if (in_len < 12) {
                return (B_FALSE);
        }

        if (IS_IQN_NAME(in_name)) {
                /*
                 * IQN names are iqn.yyyy-mm.<xxx>
                 */
                if ((!isdigit(in_name[4])) ||
                    (!isdigit(in_name[5])) ||
                    (!isdigit(in_name[6])) ||
                    (!isdigit(in_name[7])) ||
                    (in_name[8] != '-') ||
                    (!isdigit(in_name[9])) ||
                    (!isdigit(in_name[10])) ||
                    (in_name[11] != '.')) {
                        return (B_FALSE);
                }

                (void) strncpy(month, &(in_name[9]), 2);
                month[2] = '\0';

                i = atoi(month);
                if ((i < 0) || (i > 12)) {
                        return (B_FALSE);
                }

                /*
                 * RFC 3722: if using only ASCII chars, only the following
                 * chars are allowed: dash, dot, colon, lower case a-z, 0-9.
                 * We allow upper case names, which should be folded
                 * to lower case names later.
                 */
                for (i = 12; i < in_len; i++) {
                        char c = in_name[i];

                        if ((c != '-') && (c != '.') && (c != ':') &&
                            !isalpha(c) && !isdigit(c)) {
                                return (B_FALSE);
                        }
                }

                /* Finally, validate the overall length, in wide chars */
                in_len = mbstowcs(NULL, in_name, 0);
                if (in_len > ISCSI_NAME_LEN_MAX) {
                        return (B_FALSE);
                }
        } else if (IS_EUI_NAME(in_name)) {
                /*
                 * EUI names are "eui." + 16 hex chars
                 */
                if (in_len != 20) {
                        return (B_FALSE);
                }

                for (i = 4; i < in_len; i++) {
                        if (!isxdigit(in_name[i])) {
                                return (B_FALSE);
                        }
                }
        } else {
                return (B_FALSE);
        }

        return (B_TRUE);
}

static boolean_t
is_iscsit_enabled(void)
{
        char            *state;

        state = smf_get_state(ISCSIT_FMRI);
        if (state != NULL) {
                if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0) {
                        free(state);
                        return (B_TRUE);
                }
                free(state);
        }

        return (B_FALSE);
}

/*
 * Function:  canonical_iscsi_name()
 *
 * Fold the iqn iscsi name to lower-case and the EUI-64 identifier of
 * the eui iscsi name to upper-case.
 * Ensures the passed-in string is a valid IQN or EUI iSCSI name
 */
void
canonical_iscsi_name(char *tgt)
{
        if (IS_IQN_NAME(tgt)) {
                /* lowercase iqn names */
                iqnstr(tgt);
        } else {
                /* uppercase EUI-64 identifier */
                euistr(tgt);
        }
}

/*
 * Fold an iqn name to lower-case.
 */
static void
iqnstr(char *s)
{
        if (s != NULL) {
                while (*s) {
                        *s = tolower(*s);
                        s++;
                }
        }
}

/*
 * Fold the EUI-64 identifier of a eui name to upper-case.
 */
static void
euistr(char *s)
{
        if (s != NULL) {
                char *l = s + 4;
                while (*l) {
                        *l = toupper(*l);
                        l++;
                }
        }
}

static void
free_empty_errlist(nvlist_t **errlist)
{
        if (errlist != NULL && *errlist != NULL) {
                assert(fnvlist_num_pairs(*errlist) == 0);
                nvlist_free(*errlist);
                *errlist = NULL;
        }
}