libiscsit.c revision a6d42e7d71324c5193c3b94d57d96ba2925d52e1
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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 <libstmf.h>
#include <libiscsit.h>
#include <sys/iscsit/iscsit_common.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 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);
/*
* 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;
}
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);
}
iscsit_fd = open(ISCSIT_NODE, O_RDWR|O_EXCL);
if (iscsit_fd == -1) {
ret = errno;
return (ret);
}
ret = it_config_to_nv(cfg, &cfgnv);
if (ret == 0) {
ret = nvlist_size(cfgnv, &pnv_size, NV_ENCODE_NATIVE);
}
if (ret == 0) {
packednv = malloc(pnv_size);
if (!packednv) {
ret = ENOMEM;
} else {
ret = nvlist_pack(cfgnv, &packednv, &pnv_size,
NV_ENCODE_NATIVE, 0);
}
}
/*
* Send the changes to the kernel first, for now. Kernel
* will be the final sanity check before config is saved
* persistently.
*
* XXX - this leaves open the simultaneous-change hole
* that STMF was trying to solve, but is a better sanity
* check. Final decision on save order/config generation
* number TBD.
*/
if (ret == 0) {
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;
}
}
/*
* Before saving the config persistently, remove any
* PROP_OLD_TARGET_NAME entries. This is only interesting to
* the active service.
*/
if (ret == 0) {
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);
}
}
}
/*
* 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);
}
}
}
(void) close(iscsit_fd);
if (packednv) {
free(packednv);
}
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;
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(errlist, 0, 0);
}
/*
* 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);
}
/* 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, *errlist);
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(proplist,
PROP_RADIUS_SECRET, bsecret);
}
}
}
if (ret == 0) {
ret = nvlist_merge(cprops, proplist, 0);
}
/* 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);
}
if (ret == 0) {
ret = it_validate_configprops(cprops, *errlist);
}
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->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->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);
}
}
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
*/
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 = tgt_name;
char buf[ISCSI_NAME_LEN_MAX + 1];
if (!cfg || !tgt) {
return (EINVAL);
}
if (!namep) {
/* generate a name */
ret = it_iqn_generate(buf, sizeof (buf), NULL);
if (ret != 0) {
return (ret);
}
namep = buf;
} else {
/* validate the passed-in name */
if (!validate_iscsi_name(namep)) {
return (EFAULT);
}
}
/* make sure this name isn't already on the list */
cfgtgt = cfg->config_tgt_list;
while (cfgtgt != NULL) {
if (strcmp(namep, cfgtgt->tgt_name) == 0) {
return (EEXIST);
}
cfgtgt = cfgtgt->tgt_next;
}
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 *tprops = NULL;
char *val = NULL;
if (!cfg || !tgt || !proplist) {
return (EINVAL);
}
if (errlist) {
(void) nvlist_alloc(errlist, 0, 0);
}
/*
* 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) {
ret = nvlist_merge(tprops, proplist, 0);
}
/* 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, *errlist);
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, *errlist);
}
if (ret != 0) {
if (tprops) {
nvlist_free(tprops);
}
return (ret);
}
if (tgt->tgt_properties) {
nvlist_free(tgt->tgt_properties);
}
tgt->tgt_properties = tprops;
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 (strcmp(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->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->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;
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->next;
}
if (!ptr) {
return;
}
if (prev) {
prev->next = ptr->next;
} else {
tpg->tpg_portal_list = ptr->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 (strcmp(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 *iprops = NULL;
char *val = NULL;
if (!ini || !proplist) {
return (EINVAL);
}
if (errlist) {
(void) nvlist_alloc(errlist, 0, 0);
}
/*
* 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) {
ret = nvlist_merge(iprops, proplist, 0);
}
/* 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, *errlist);
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, *errlist);
}
if (ret != 0) {
if (iprops) {
nvlist_free(iprops);
}
return (ret);
}
if (ini->ini_properties) {
nvlist_free(ini->ini_properties);
}
ini->ini_properties = iprops;
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 (strcmp(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;
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.
*/
char *rad = NULL;
if (sockaddr_to_str(&sa, &rad) == 0) {
(void) nvlist_add_string(nvl,
name, rad);
}
}
} else {
/* unrecognized property */
PROPERR(errs, name, gettext("unrecognized property"));
errcnt++;
}
}
/*
* 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, "iqn.1986-03.com.sun:"
"%02d:%s.%s", TARGET_NAME_VERS, id_str, opt_iqn_suffix);
} else {
ret = snprintf(iqn_buf, iqn_buf_len, "iqn.1986-03.com.sun:"
"%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 (strncasecmp(in_name, "iqn.", 4) == 0) {
/*
* 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);
}
/* 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 (strncasecmp(in_name, "eui.", 4) == 0) {
/*
* 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);
}