OpenSolaris_b135/cmd/itadm/itadm.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <strings.h>
#include <ctype.h>
#include <libnvpair.h>
#include <libintl.h>
#include <libgen.h>
#include <pwd.h>
#include <auth_attr.h>
#include <secdb.h>
#include <libscf.h>
#include <limits.h>
#include <locale.h>

#include <libstmf.h>
#include <libiscsit.h>

/* what's this used for?? */
#define	ITADM_VERSION	"1.0"

/* SMF service info */
#define	ISCSIT_SVC	"svc:/network/iscsi/target:default"

#define	STMF_STALE(ret) {\
	if (ret == STMF_ERROR_PROV_DATA_STALE) {\
		output_config_error(ret, NULL);\
	} else if (ret != 0) {\
		output_config_error(ret,\
		    gettext("Configuration change failed"));\
	}\
}

#define	ITADM_CHKAUTH(sec) {\
	if (!chkauthattr(sec, itadm_uname)) {\
		(void) fprintf(stderr,\
		    gettext("Error, operation requires authorization %s"),\
		    sec);\
		(void) fprintf(stderr, "\n");\
		return (1);\
	}\
}


static struct option itadm_long[] = {
	{"alias",		required_argument,	NULL, 'l'},
	{"auth-method",		required_argument,	NULL, 'a'},
	{"chap-secret",		no_argument,		NULL, 's'},
	{"chap-secret-file",	required_argument,	NULL, 'S'},
	{"chap-user",		required_argument,	NULL, 'u'},
	{"force",		no_argument,		NULL, 'f'},
	{"help",		no_argument,		NULL, 'h'},
	{"help",		no_argument,		NULL, '?'},
	{"isns",		required_argument,	NULL, 'i'},
	{"isns-server",		required_argument,	NULL, 'I'},
	{"node-name",		required_argument,	NULL, 'n'},
	{"radius-secret",	no_argument,		NULL, 'd'},
	{"radius-secret-file",	required_argument,	NULL, 'D'},
	{"radius-server",	required_argument,	NULL, 'r'},
	{"tpg-tag",		required_argument,	NULL, 't'},
	{"verbose",		no_argument,		NULL, 'v'},
	{"version",		no_argument,		NULL, 'V'},
	{NULL, 0, NULL, 0}
};

char c_tgt[] = "itadm create-target [-a radius|chap|none|default] [-s] \
[-S chap-secret-path] [-u chap-username] [-n target-node-name] \
[-l alias] [-t tpg-name[,tpg-name,...]]";

static char m_tgt[] = "itadm modify-target [-a radius|chap|none|default] [-s] \
[-S chap-secret-path] [-u chap-username] [-n new-target-node-name] \
[-l alias] [-t tpg-name[,tpg-name,...]] target-node-name";

static char d_tgt[] = "itadm delete-target [-f] target-node-name";

static char l_tgt[] = "itadm list-target [-v] [target-node-name]";

static char c_tpg[] = "itadm create-tpg tpg-name IP-address[:port] \
[IP-address[:port]] [...]";

static char l_tpg[] = "itadm list-tpg [-v] [tpg-name]";

static char d_tpg[] = "itadm delete-tpg [-f] tpg-name";

static char c_ini[] = "itadm create-initiator [-s] [-S chap-secret-path] \
[-u chap-username] initiator-node-name";

static char m_ini[] = "itadm modify-initiator [-s] [-S chap-secret-path] \
[-u chap-username] initiator-node-name";

static char l_ini[] = "itadm list-initiator [-v] initiator-node-name";

static char d_ini[] = "itadm delete-inititator initiator-node-name";

static char m_def[] = "itadm modify-defaults [-a radius|chap|none] \
[-r IP-address[:port]] [-d] [-D radius-secret-path] [-i enable|disable] \
[-I IP-address[:port][,IP-adddress[:port]]]";

static char l_def[] = "itadm list-defaults";


/* keep the order of this enum in the same order as the 'subcmds' struct */
typedef enum {
	CREATE_TGT,
	MODIFY_TGT,
	DELETE_TGT,
	LIST_TGT,
	CREATE_TPG,
	DELETE_TPG,
	LIST_TPG,
	CREATE_INI,
	MODIFY_INI,
	LIST_INI,
	DELETE_INI,
	MODIFY_DEF,
	LIST_DEF,
	NULL_SUBCMD	/* must always be last! */
} itadm_sub_t;

typedef struct {
	char		*name;
	char		*shortopts;
	char		*usemsg;
} itadm_subcmds_t;

static itadm_subcmds_t	subcmds[] = {
	{"create-target", ":a:sS:u:n:l:t:h?", c_tgt},
	{"modify-target", ":a:sS:u:n:l:t:h?", m_tgt},
	{"delete-target", ":fh?", d_tgt},
	{"list-target", ":vh?", l_tgt},
	{"create-tpg", ":h?", c_tpg},
	{"delete-tpg", ":fh?", d_tpg},
	{"list-tpg", ":vh?", l_tpg},
	{"create-initiator", ":sS:u:h?", c_ini},
	{"modify-initiator", ":sS:u:h?", m_ini},
	{"list-initiator", ":vh?", l_ini},
	{"delete-initiator", ":h?", d_ini},
	{"modify-defaults", ":a:r:dD:i:I:h?", m_def},
	{"list-defaults", ":h?", l_def},
	{NULL, ":h?", NULL},
};

/* used for checking if user is authorized */
static char *itadm_uname = NULL;

/* prototypes */
static int
itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
    char *phrase);

static int
itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num);

static int
create_target(char *tgt, nvlist_t *proplist);

static int
modify_target(char *tgt, char *new, nvlist_t *proplist);

static int
delete_target(char *tgt, boolean_t force);

static int
list_target(char *tgt, boolean_t verbose, boolean_t script);

static int
create_tpg(char *tpg, int addrc, char **addrs);

static int
list_tpg(char *tpg, boolean_t verbose, boolean_t script);

static int
delete_tpg(char *tpg, boolean_t force);

static int
modify_initiator(char *ini, nvlist_t *proplist, boolean_t create);

static int
list_initiator(char *ini, boolean_t verbose, boolean_t script);

static int
delete_initiator(char *ini);

static int
modify_defaults(nvlist_t *proplist);

static int
list_defaults(boolean_t script);

static void
tag_name_to_num(char *tagname, uint16_t *tagnum);

/* prototype from iscsit_common.h */
extern int
sockaddr_to_str(struct sockaddr_storage *sa, char **addr);

static void output_config_error(int error_code, char *msg);

int
main(int argc, char *argv[])
{
	int		ret = 0;
	int		idx = NULL_SUBCMD;
	char		c;
	int		newargc = argc;
	char		**newargv = NULL;
	char		*objp;
	int		itind = 0;
	nvlist_t	*proplist = NULL;
	boolean_t	verbose = B_FALSE;
	boolean_t	scripting = B_FALSE;
	boolean_t	tbool;
	char		*targetname = NULL;
	char		*propname;
	boolean_t	force = B_FALSE;
	struct passwd	*pwd = NULL;
	uint32_t	count = 0;
	char		*smfstate = NULL;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	if (argc < 2) {
		ret = 1;
		goto usage_error;
	}

	for (idx = 0; subcmds[idx].name != NULL; idx++) {
		if (strcmp(argv[1], subcmds[idx].name) == 0) {
			break;
		}
	}


	/* get the caller's user name for subsequent chkauthattr() calls */
	pwd = getpwuid(getuid());
	if (pwd == NULL) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Could not determine callers user name"));
		return (1);
	}

	itadm_uname = strdup(pwd->pw_name);

	/* increment past command & subcommand */
	newargc--;
	newargv = &(argv[1]);

	ret = nvlist_alloc(&proplist, NV_UNIQUE_NAME, 0);
	if (ret != 0) {
		ret = errno;
		output_config_error(ret, gettext("Could not allocate nvlist"));
		ret = 1;
		goto usage_error;
	}

	while ((ret == 0) && (newargv)) {
		c = getopt_long(newargc, newargv, subcmds[idx].shortopts,
		    itadm_long, &itind);
		if (c == -1) {
			break;
		}

		switch (c) {
			case 0:
				/* flag set by getopt */
				break;
			case 'a':
				ret = nvlist_add_string(proplist,
				    "auth", optarg);
				break;
			case 'd':
				ret = itadm_get_password(proplist,
				    "radiussecret", NULL,
				    gettext("Enter RADIUS secret: "));
				break;
			case 'D':
				ret = itadm_get_password(proplist,
				    "radiussecret", optarg, NULL);
				break;
			case 'f':
				force = B_TRUE;
				break;
			case '?':
				/*
				 * '?' is returned for both unrecognized
				 * options and if explicitly provided on
				 * the command line.  The latter should
				 * be handled the same as -h.
				 */
				if (strcmp(newargv[optind-1], "-?") != 0) {
					(void) fprintf(stderr,
					    gettext("Unrecognized option %s"),
					    newargv[optind-1]);
					(void) fprintf(stderr, "\n");
					ret = 1;
				}
				goto usage_error;
			case 'h':
				goto usage_error;
			case 'i':
				if (strncmp(optarg, "enable", strlen(optarg))
				    == 0) {
					tbool = B_TRUE;
				} else if (strncmp(optarg, "disable",
				    strlen(optarg)) == 0) {
					tbool = B_FALSE;
				} else {
					(void) fprintf(stderr, "%s\n",
					    gettext("invalid value for -i"));
					ret = 1;
					break;
				}
				ret = nvlist_add_boolean_value(proplist,
				    "isns", tbool);
				break;
			case 'I':
				/* possibly multi-valued */
				ret = itadm_opt_to_arr(proplist,
				    "isnsserver", optarg, &count);
				if ((ret == 0) && (count > 8)) {
					(void) fprintf(stderr, "%s\n",
					    gettext(
					    "Too many iSNS servers specified, "
					    "maximum of 8 allowed"));
					ret = 1;
				}
				break;
			case 'l':
				ret = nvlist_add_string(proplist,
				    "alias", optarg);
				break;
			case 'n':
				targetname = strdup(optarg);
				if (targetname == NULL) {
					ret = ENOMEM;
				}
				break;
			case 'r':
				ret = nvlist_add_string(proplist,
				    "radiusserver", optarg);
				break;
			case 's':
				if ((idx == CREATE_TGT) ||
				    (idx == MODIFY_TGT)) {
					propname = "targetchapsecret";
				} else {
					propname = "chapsecret";
				}
				ret = itadm_get_password(proplist,
				    propname, NULL,
				    gettext("Enter CHAP secret: "));
				break;
			case 'S':
				if ((idx == CREATE_TGT) ||
				    (idx == MODIFY_TGT)) {
					propname = "targetchapsecret";
				} else {
					propname = "chapsecret";
				}
				ret = itadm_get_password(proplist,
				    propname, optarg, NULL);
				break;
			case 't':
				/* possibly multi-valued */
				ret = itadm_opt_to_arr(proplist,
				    "tpg-tag", optarg, NULL);
				break;
			case 'u':
				if ((idx == CREATE_TGT) ||
				    (idx == MODIFY_TGT)) {
					propname = "targetchapuser";
				} else {
					propname = "chapuser";
				}
				ret = nvlist_add_string(proplist,
				    propname, optarg);
				break;
			case 'v':
				verbose = B_TRUE;
				break;
			case ':':
				(void) fprintf(stderr,
				    gettext("Option %s requires an operand"),
				    newargv[optind-1]);
				(void) fprintf(stderr, "\n");

				/* fall through to default */
			default:
				ret = 1;
				break;
		}
	}

	if (ret != 0) {
		goto usage_error;
	}

	/* after getopt() to allow handling of -h option */
	if ((itadm_sub_t)idx == NULL_SUBCMD) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no subcommand specified"));
		ret = 1;
		goto usage_error;
	}

	/*
	 * some subcommands take multiple operands, so adjust now that
	 * getopt is complete
	 */
	newargc -= optind;
	if (newargc == 0) {
		newargv = NULL;
		objp = NULL;
	} else {
		newargv = &(newargv[optind]);
		objp = newargv[0];
	}

	if (objp == NULL) {
		switch ((itadm_sub_t)idx) {
		case MODIFY_TGT:
		case DELETE_TGT:
		case CREATE_TPG:
		case DELETE_TPG:
		case CREATE_INI:
		case MODIFY_INI:
		case DELETE_INI:
			/* These subcommands need at least one operand */
			(void) fprintf(stderr,
			    gettext("Error, %s requires an operand"),
			    subcmds[idx].name);
			(void) fprintf(stderr, "\n");

			ret = 1;
			goto usage_error;
		default:
			break;
		}
	}

	if (newargc > 1) {
		switch ((itadm_sub_t)idx) {
		case MODIFY_TGT:
		case DELETE_TGT:
		case LIST_TGT:
		case DELETE_TPG:
		case LIST_TPG:
		case CREATE_INI:
		case MODIFY_INI:
		case LIST_INI:
		case DELETE_INI:
			/* These subcommands should have at most one operand */
			(void) fprintf(stderr,
			    gettext("Error, %s accepts only a single operand"),
			    subcmds[idx].name);
			(void) fprintf(stderr, "\n");

			ret = 1;
			goto usage_error;

		default:
			break;
		}
	}

	if (newargc > 0) {
		switch ((itadm_sub_t)idx) {
		case CREATE_TGT:
		case MODIFY_DEF:
		case LIST_DEF:
			/* These subcommands do not support an operand */
			(void) fprintf(stderr,
			    gettext("Error, %s does not support any operands"),
			    subcmds[idx].name);
			(void) fprintf(stderr, "\n");

			ret = 1;
			goto usage_error;

		default:
			break;
		}
	}

	/*
	 * XXX - this should probably get pushed down to the library
	 * depending on the decision to allow/disallow configuratoin
	 * without the service running.
	 */
	/*
	 * Make sure iSCSI target service is enabled before
	 * proceeding.
	 */
	smfstate = smf_get_state(ISCSIT_SVC);
	if (!smfstate ||
	    (strcmp(smfstate, SCF_STATE_STRING_ONLINE) != 0)) {
		(void) fprintf(stderr, "%s\n",
		    gettext("The iSCSI target service must be online "
		    "before running this command."));
		(void) fprintf(stderr,
		    gettext("Use 'svcadm enable -r %s'"), ISCSIT_SVC);
		(void) fprintf(stderr, "\n");
		(void) fprintf(stderr, "%s\n",
		    gettext("to enable the service and its prerequisite "
		    "services and/or"));
		(void) fprintf(stderr,
		    gettext("'svcs -x %s' to determine why it is not online."),
		    ISCSIT_SVC);
		(void) fprintf(stderr, "\n");

		return (1);
	}

	switch ((itadm_sub_t)idx) {
		case CREATE_TGT:
			/*
			 * OK for targetname to be NULL here.  If the
			 * user did not specify a target name,
			 * one will be generated.
			 */
			ret = create_target(targetname, proplist);
			break;
		case MODIFY_TGT:
			ret = modify_target(objp, targetname, proplist);
			break;
		case DELETE_TGT:
			ret = delete_target(objp, force);
			break;
		case LIST_TGT:
			ret = list_target(objp, verbose, scripting);
			break;
		case CREATE_TPG:
			ret = create_tpg(objp, newargc - 1, &(newargv[1]));
			break;
		case DELETE_TPG:
			ret = delete_tpg(objp, force);
			break;
		case LIST_TPG:
			ret = list_tpg(objp, verbose, scripting);
			break;
		case CREATE_INI:
			ret = modify_initiator(objp, proplist, B_TRUE);
			break;
		case MODIFY_INI:
			ret = modify_initiator(objp, proplist, B_FALSE);
			break;
		case LIST_INI:
			ret = list_initiator(objp, verbose, scripting);
			break;
		case DELETE_INI:
			ret = delete_initiator(objp);
			break;
		case MODIFY_DEF:
			ret = modify_defaults(proplist);
			break;
		case LIST_DEF:
			ret = list_defaults(scripting);
			break;
		default:
			ret = 1;
			goto usage_error;
	}

	if (ret != 0) {
		(void) fprintf(stderr,
		    gettext("itadm %s failed with error %d"),
		    subcmds[idx].name, ret);
		(void) fprintf(stderr, "\n");
	}
	return (ret);

usage_error:
	if (subcmds[idx].name) {
		(void) printf("%s\n", gettext(subcmds[idx].usemsg));
	} else {
		/* overall usage */
		(void) printf("%s\n\n", gettext("itadm usage:"));
		for (idx = 0; subcmds[idx].name != NULL; idx++) {
			if (!subcmds[idx].usemsg) {
				continue;
			}
			(void) printf("\t%s\n", gettext(subcmds[idx].usemsg));
		}
	}

	return (ret);
}

static int
create_target(char *tgt, nvlist_t *proplist)
{
	int		ret;
	it_config_t	*cfg = NULL;
	it_tgt_t	*tgtp;
	char		**tags = NULL;
	uint32_t	count = 0;
	nvlist_t	*errlist = NULL;
	int		i;
	it_tpg_t	*tpg = NULL;
	uint16_t	tagid = 0;
	it_tpgt_t	*tpgt;
	char		*sec = "solaris.smf.modify.stmf";
	boolean_t	did_it_config_load = B_FALSE;

	ITADM_CHKAUTH(sec);

	if (tgt) {
		/*
		 * Validate target name.
		 */
		if (!IS_IQN_NAME(tgt) && !IS_EUI_NAME(tgt)) {
			(void) fprintf(stderr, gettext("Invalid name %s"),
			    tgt);
			(void) fprintf(stderr, "\n");
			return (EINVAL);
		}
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		goto done;
	}

	did_it_config_load = B_TRUE;

	ret = it_tgt_create(cfg, &tgtp, tgt);
	if (ret != 0) {
		if (ret == EFAULT) {
			(void) fprintf(stderr,
			    gettext("Invalid iSCSI name %s"), tgt);
			(void) fprintf(stderr, "\n");
		} else if (ret == EEXIST) {
			(void) fprintf(stderr,
			    gettext("iSCSI target %s already configured"),
			    tgt);
			(void) fprintf(stderr, "\n");
		} else if (ret == E2BIG) {
			(void) fprintf(stderr,
			    gettext("Maximum of %d iSCSI targets"),
			    MAX_TARGETS);
			(void) fprintf(stderr, "\n");
		} else {
			output_config_error(ret,
			    gettext("Error creating target"));
		}

		goto done;
	}

	/* set the target portal group tags */
	ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
	    &count);

	if (ret == ENOENT) {
		/* none specified.  is this ok? */
		ret = 0;
	} else if (ret != 0) {
		output_config_error(ret, gettext("Internal error"));
		goto done;
	}

	/* special case, don't set any TPGs */
	if (tags && (count == 1) && (strcmp("default", tags[0]) == 0)) {
		count = 0;
	}

	for (i = 0; i < count; i++) {
		if (!tags[i]) {
			continue;
		}

		/* see that all referenced groups are already defined */
		tpg = cfg->config_tpg_list;
		while (tpg != NULL) {
			if (strcmp(tags[i], tpg->tpg_name) == 0) {
				break;
			}

			tpg = tpg->tpg_next;
		}
		if (tpg == NULL) {
			(void) fprintf(stderr,
			    gettext("Invalid tpg-tag %s, tag not defined"),
			    tags[i]);
			(void) fprintf(stderr, "\n");
			ret = 1;
			goto done;
		}

		/* generate the tag number to use */
		tag_name_to_num(tags[i], &tagid);

		ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
		if (ret != 0) {
			(void) fprintf(stderr, gettext(
			    "Could not add target portal group tag %s: "),
			    tags[i]);
			output_config_error(ret, NULL);
			goto done;
		}
		tagid++;
	}

	/* remove the tags from the proplist before continuing */
	if (tags) {
		(void) nvlist_remove_all(proplist, "tpg-tag");
	}

	ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
	if (ret != 0) {
		(void) fprintf(stderr,
		    gettext("Error setting target properties: %d"), ret);
		(void) fprintf(stderr, "\n");
		if (errlist) {
			nvpair_t	*nvp = NULL;
			char		*nn;
			char		*nv;

			while ((nvp = nvlist_next_nvpair(errlist, nvp))
			    != NULL) {
				nv = NULL;

				nn = nvpair_name(nvp);
				(void) nvpair_value_string(nvp, &nv);

				if (nv != NULL) {
					(void) fprintf(stderr, "\t%s: %s\n",
					    nn, nv);
				}
			}

			nvlist_free(errlist);
		}
		goto done;
	}

	if (ret == 0) {
		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	}

done:
	if (ret == 0) {
		(void) printf(gettext("Target %s successfully created"),
		    tgtp->tgt_name);
		(void) printf("\n");
	}

	if (did_it_config_load)
		it_config_free(cfg);

	return (ret);
}

int
list_target(char *tgt, boolean_t verbose, boolean_t script)
{
	int		ret;
	it_config_t	*cfg;
	it_tgt_t	*ptr;
	boolean_t	found = B_FALSE;
	boolean_t	first = B_TRUE;
	boolean_t	first_tag = B_TRUE;
	char		*gauth = "none";
	char		*galias = "-";
	char		*auth;
	char		*alias;
	char		*chapu;
	char		*chaps;
	it_tpgt_t	*tagp;
	char		*sec = "solaris.smf.read.stmf";
	stmfDevid	devid;
	stmfSessionList	*sess = NULL;
	stmfTargetProperties	props;
	char		*state;
	int		num_sessions;

	ITADM_CHKAUTH(sec);

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ptr = cfg->config_tgt_list;

	/* grab global defaults for auth, alias */
	if (cfg->config_global_properties) {
		(void) nvlist_lookup_string(cfg->config_global_properties,
		    "alias", &galias);
		(void) nvlist_lookup_string(cfg->config_global_properties,
		    "auth", &gauth);
	}

	for (; ptr != NULL; ptr = ptr->tgt_next) {
		if (found) {
			break;
		}

		if (tgt) {
			/*
			 * We do a case-insensitive match in case
			 * a non-lower case value got stored.
			 */
			if (strcasecmp(tgt, ptr->tgt_name) != 0) {
				continue;
			} else {
				found = B_TRUE;
			}
		}

		state = "-";
		num_sessions = 0;
		sess = NULL;

		/*
		 * make a best effort to retrieve target status and
		 * number of active sessions from STMF.
		 */
		ret = stmfDevidFromIscsiName(ptr->tgt_name, &devid);
		if (ret == STMF_STATUS_SUCCESS) {
			ret = stmfGetTargetProperties(&devid, &props);
			if (ret == STMF_STATUS_SUCCESS) {
				if (props.status == STMF_TARGET_PORT_ONLINE) {
					state = "online";
				} else {
					state = "offline";
				}
			}
		}
		if (ret == STMF_STATUS_SUCCESS) {
			ret = stmfGetSessionList(&devid, &sess);
			if (ret == STMF_STATUS_SUCCESS) {
				num_sessions = sess->cnt;
				free(sess);
			}
		}

		/* reset ret so we don't return an error */
		ret = 0;

		if (!script && first) {
			(void) printf("%-61s%-9s%-9s\n", "TARGET NAME",
			    "STATE", "SESSIONS");
			first = B_FALSE;
		}

		if (!script) {
			/*
			 * try not to let columns run into each other.
			 * Stick a tab after too-long fields.
			 * Lengths chosen are for the 'common' cases.
			 */
			(void) printf("%-61s", ptr->tgt_name);
			if (strlen(ptr->tgt_name) > 60) {
				(void) printf("\t");
			}
			(void) printf("%-9s%-9d", state, num_sessions);
		} else {
			(void) printf("%s\t%s\t%d", ptr->tgt_name,
			    state, num_sessions);
		}

		if (!verbose) {
			(void) printf("\n");
			continue;
		}

		auth = gauth;
		alias = galias;
		chapu = "-";
		chaps = "unset";

		if (ptr->tgt_properties) {
			(void) nvlist_lookup_string(ptr->tgt_properties,
			    "auth", &auth);
			(void) nvlist_lookup_string(ptr->tgt_properties,
			    "alias", &alias);
			if (nvlist_exists(ptr->tgt_properties,
			    "targetchapsecret")) {
				chaps = "set";
			}
			(void) nvlist_lookup_string(ptr->tgt_properties,
			    "targetchapuser", &chapu);
		}

		if (!script) {
			(void) printf("\n\t%-20s\t%s\n\t%-20s\t%s %s\n"
			    "\t%-20s\t%s\n\t%-20s\t%s\n\t%-20s\t",
			    "alias:", alias, "auth:", auth,
			    ((auth == gauth) ? "(defaults)" : ""),
			    "targetchapuser:",
			    chapu, "targetchapsecret:", chaps, "tpg-tags:");
		} else {
			(void) printf("\t%s\t%s %s\t%s\t%s\t",
			    alias, auth,
			    ((auth == gauth) ? "(defaults)" : ""),
			    chapu, chaps);
		}

		first_tag = B_TRUE;
		tagp = ptr->tgt_tpgt_list;
		for (; tagp != NULL; tagp = tagp->tpgt_next) {
			if (!first_tag) {
				(void) printf(",");
			} else {
				first_tag = B_FALSE;
			}
			(void) printf("%s = %d",
			    tagp->tpgt_tpg_name, tagp->tpgt_tag);
		}

		if (first_tag) {
			/* didn't find any */
			(void) printf("default");
		}

		(void) printf("\n");
	}

	if (tgt && (!found)) {
		(void) fprintf(stderr,
		    gettext("Target %s not found!"), tgt);
		(void) fprintf(stderr, "\n");
		ret = 1;
	}

	it_config_free(cfg);

	return (ret);
}

int
delete_target(char *tgt, boolean_t force)
{
	int		ret;
	it_config_t	*cfg;
	it_tgt_t	*ptr;
	char		*sec = "solaris.smf.modify.stmf";

	ITADM_CHKAUTH(sec);

	if (!tgt) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no target specified"));
		return (EINVAL);
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ptr = cfg->config_tgt_list;
	while (ptr) {
		/*
		 * We do a case-insensitive match in case
		 * a non-lower case value got stored.
		 */
		if (strcasecmp(ptr->tgt_name, tgt) == 0) {
			break;
		}

		ptr = ptr->tgt_next;
	}

	if (ptr) {
		ret = it_tgt_delete(cfg, ptr, force);

		if (ret != 0) {
			if (ret == EBUSY) {
				(void) fprintf(stderr,
				    gettext("The target is online or busy. "
				    "Use the -f (force) option, or "
				    "'stmfadm offline-target %s'"), tgt);
				(void) fprintf(stderr, "\n");
			} else {
				output_config_error(ret, gettext(
				    "Error deleting target"));
			}
		}

		if (ret == 0) {
			ret = it_config_commit(cfg);
			STMF_STALE(ret);
		}
	} else {
		(void) fprintf(stderr,
		    gettext("Target %s not found"), tgt);
		(void) fprintf(stderr, "\n");
		ret = 1;
	}

	it_config_free(cfg);

	return (ret);
}

static int
modify_target(char *tgt, char *newname, nvlist_t *proplist)
{
	int		ret;
	it_config_t	*cfg = NULL;
	it_tgt_t	*ptr = NULL;
	it_tgt_t	*tgtp = NULL;
	char		**tags = NULL;
	uint32_t	count = 0;
	nvlist_t	*errlist = NULL;
	int		i;
	it_tpg_t	*tpg = NULL;
	uint16_t	tagid;
	it_tpgt_t	*tpgt = NULL;
	char		*sec = "solaris.smf.modify.stmf";
	boolean_t	did_it_config_load = B_FALSE;

	ITADM_CHKAUTH(sec);

	/* XXX:  Do we need to offline anything here too? */

	if (!tgt) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no target specified"));
		ret = EINVAL;
		goto done;
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		goto done;
	}

	did_it_config_load = B_TRUE;

	/*
	 * If newname is specified, ensure it is a valid name.
	 */
	if (newname) {
		if (!validate_iscsi_name(newname)) {
			(void) fprintf(stderr,
			    gettext("Invalid iSCSI name %s"), newname);
			(void) fprintf(stderr, "\n");
			ret = 1;
			goto done;
		}
	}

	/*
	 * Loop through to verify that the target to be modified truly
	 * exists.  If this target is to be renamed, ensure the new
	 * name is not already in use.
	 */
	ptr = cfg->config_tgt_list;
	while (ptr) {
		/*
		 * Does a target with the new name already exist?
		 */
		if (newname &&
		    (strcasecmp(newname, ptr->tgt_name) == 0)) {
			(void) fprintf(stderr,
			    gettext("A target with name %s already exists"),
			    newname);
			(void) fprintf(stderr, "\n");
			ret = 1;
			goto done;
		}

		if (strcasecmp(ptr->tgt_name, tgt) == 0) {
			tgtp = ptr;
		}

		ptr = ptr ->tgt_next;
	}

	if (!tgtp) {
		(void) fprintf(stderr,
		    gettext("Target %s not found"), tgt);
		(void) fprintf(stderr, "\n");
		ret = EINVAL;
		goto done;
	}

	/* set the target portal group tags */
	ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
	    &count);

	if (ret == ENOENT) {
		/* none specified.  is this ok? */
		ret = 0;
	} else if (ret != 0) {
		output_config_error(ret, gettext("Internal error"));
		goto done;
	}

	/* special case, remove all explicit TPGs, and don't add any */
	if (tags && (count == 1) && (strcmp("default", tags[0]) == 0)) {
		count = 0;
	}

	for (i = 0; i < count; i++) {
		if (!tags || !tags[i]) {
			continue;
		}

		/* see that all referenced groups are already defined */
		tpg = cfg->config_tpg_list;
		while (tpg != NULL) {
			if (strcmp(tags[i], tpg->tpg_name) == 0) {
				break;
			}
			tpg = tpg->tpg_next;
		}
		if (tpg == NULL) {
			(void) fprintf(stderr,
			    gettext("Invalid tpg-name %s: not defined"),
			    tags[i]);
			(void) fprintf(stderr, "\n");
			ret = 1;
			goto done;
		}
	}

	/*
	 * don't recreate tags that are already associated,
	 * remove tags not requested.
	 */
	if (tags) {
		tpgt = tgtp->tgt_tpgt_list;
		while (tpgt) {
			for (i = 0; i < count; i++) {
				if (!tags[i]) {
					continue;
				}

				if (strcmp(tpgt->tpgt_tpg_name, tags[i])
				    == 0) {
					/* non-null tags will be created */
					tags[i] = NULL;
					break;
				}
			}
			if (i == count) {
				/* one to remove */
				it_tpgt_t	*ptr = tpgt;

				tpgt = ptr->tpgt_next;
				it_tpgt_delete(cfg, tgtp, ptr);
			} else {
				tpgt = tpgt->tpgt_next;
			}
		}
	}

	/* see if there are any left to add */
	for (i = 0; i < count; i++) {
		if (!tags || !tags[i]) {
			continue;
		}

		/* generate the tag number to use */
		tag_name_to_num(tags[i], &tagid);

		ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
		if (ret != 0) {
			if (ret == E2BIG) {
				(void) fprintf(stderr, "%s\n",
				    gettext("Error, no portal tag available"));
			} else {
				(void) fprintf(stderr, gettext(
				    "Could not add target portal group"
				    " tag %s: "), tags[i]);
				output_config_error(ret, NULL);
			}
			goto done;
		}
	}

	/* remove the tags from the proplist before continuing */
	(void) nvlist_remove_all(proplist, "tpg-tag");

	/*
	 * Rename this target, if requested.  Save the old name in
	 * the property list, so the kernel knows this is a renamed
	 * target, and not a new one.
	 */
	if (newname && (strlen(newname) > 0)) {
		ret = nvlist_add_string(proplist, "oldtargetname",
		    tgtp->tgt_name);
		if (ret != 0) {
			output_config_error(ret,
			    gettext("Error renaming target"));
			goto done;
		}
		(void) strlcpy(tgtp->tgt_name, newname,
		    sizeof (tgtp->tgt_name));
	}

	ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
	if (ret != 0) {
		(void) fprintf(stderr,
		    gettext("Error setting target properties: %d"), ret);
		(void) fprintf(stderr, "\n");
		if (errlist) {
			nvpair_t	*nvp = NULL;
			char		*nn;
			char		*nv;

			while ((nvp = nvlist_next_nvpair(errlist, nvp))
			    != NULL) {
				nv = NULL;

				nn = nvpair_name(nvp);
				(void) nvpair_value_string(nvp, &nv);

				if (nv != NULL) {
					(void) fprintf(stderr, "\t%s: %s\n",
					    nn, nv);
				}
			}

			nvlist_free(errlist);
		}
		goto done;
	}

	if (ret == 0) {
		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	}

done:
	if (ret == 0) {
		(void) printf(gettext("Target %s successfully modified"),
		    tgtp->tgt_name);
		(void) printf("\n");
	}

	if (did_it_config_load)
		it_config_free(cfg);

	return (ret);
}

int
create_tpg(char *tpg, int addrc, char **addrs)
{
	int		ret;
	it_config_t	*cfg;
	it_tpg_t	*tpgp;
	int		count = 0;
	it_portal_t	*ptl;
	char		*sec = "solaris.smf.modify.stmf";
	int 		i = 0;

	ITADM_CHKAUTH(sec);

	if (!tpg) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no target portal group specified"));
		return (EINVAL);
	}

	if (strlen(tpg) > (MAX_TPG_NAMELEN - 1)) {
		(void) fprintf(stderr,
		    gettext("Target Portal Group name must be no longer "
		    "than %d characters"), (MAX_TPG_NAMELEN - 1));
		(void) fprintf(stderr, "\n");
		return (EINVAL);
	}

	if (!addrs || (addrc <= 0)) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no portal addresses specified"));
		return (EINVAL);
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	tpgp = cfg->config_tpg_list;
	while (tpgp != NULL) {
		if (strcmp(tpgp->tpg_name, tpg) == 0) {
			(void) fprintf(stderr,
			    gettext("Target Portal Group %s already exists"),
			    tpg);
			(void) fprintf(stderr, "\n");
			it_config_free(cfg);
			return (1);
		}
		tpgp = tpgp->tpg_next;
	}

	/*
	 * Ensure that the addrs don't contain commas.
	 */
	for (i = 0; i < addrc; i++) {
		if (strchr(addrs[i], ',')) {
			(void) fprintf(stderr,
			    gettext("Bad portal name %s"),
			    addrs[i]);
			(void) fprintf(stderr, "\n");

			it_config_free(cfg);
			return (EINVAL);
		}
	}

	/*
	 * Create the portal group and first portal
	 */
	ret = it_tpg_create(cfg, &tpgp, tpg, addrs[count]);
	if (ret != 0) {
		if (ret == EEXIST) {
			(void) fprintf(stderr,
			    gettext("Portal %s already in use"),
			    addrs[count]);
			(void) fprintf(stderr, "\n");
		} else {
			output_config_error(ret, gettext("Could not create the "
			    "target portal group"));
		}
		it_config_free(cfg);
		return (ret);
	}

	/*
	 * Add the remaining portals
	 */
	for (count = 1; count < addrc; count++) {
		if (!addrs[count]) {
			continue;
		}

		ret = it_portal_create(cfg, tpgp, &ptl, addrs[count]);
		if (ret != 0) {
			if (ret == EEXIST) {
				(void) fprintf(stderr,
				    gettext("Portal %s already in use"),
				    addrs[count]);
				(void) fprintf(stderr, "\n");
			} else {
				(void) fprintf(stderr,
				    gettext("Error adding portal %s: "),
				    addrs[count]);
				output_config_error(ret, NULL);
				break;
			}
		}
	}

	if (ret == 0) {
		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	}

	it_config_free(cfg);

	return (ret);
}

static int
list_tpg(char *tpg, boolean_t verbose, boolean_t script)
{
	int		ret;
	it_config_t	*cfg;
	it_tpg_t	*ptr;
	boolean_t	found = B_FALSE;
	it_portal_t	*portal;
	boolean_t	first = B_TRUE;
	boolean_t	first_portal;
	char		*pstr;
	char		*sec = "solaris.smf.read.stmf";

	ITADM_CHKAUTH(sec);

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ptr = cfg->config_tpg_list;

	for (; ptr != NULL; ptr = ptr->tpg_next) {
		if (found) {
			break;
		}

		if (tpg) {
			if (strcmp(tpg, ptr->tpg_name) != 0) {
				continue;
			} else {
				found = B_TRUE;
			}
		}

		if (!script && first) {
			(void) printf("%-30s%-9s\n", "TARGET PORTAL GROUP",
			    "PORTAL COUNT");
			first = B_FALSE;
		}

		if (!script) {
			(void) printf("%-30s", ptr->tpg_name);
			if (strlen(ptr->tpg_name) > 30) {
				(void) printf("\t");
			}
			(void) printf("%-9d", ptr->tpg_portal_count);
		} else {
			(void) printf("%s\t%d", ptr->tpg_name,
			    ptr->tpg_portal_count);
		}

		if (!verbose) {
			(void) printf("\n");
			continue;
		}

		if (!script) {
			(void) printf("\n    portals:");
		}

		first_portal = B_TRUE;

		portal = ptr->tpg_portal_list;
		for (; portal != NULL; portal = portal->next) {
			ret = sockaddr_to_str(&(portal->portal_addr), &pstr);
			if (ret != 0) {
				/* invalid addr? */
				continue;
			}
			if (!first_portal) {
				(void) printf(",");
			} else {
				(void) printf("\t");
				first_portal = B_FALSE;
			}

			(void) printf("%s", pstr);
			free(pstr);
		}

		if (first_portal) {
			/* none found */
			(void) printf("\t<none>");
		}

		(void) printf("\n");
	}

	if (tpg && (!found)) {
		(void) fprintf(stderr,
		    gettext("Target Portal Group %s not found!\n"), tpg);
		(void) fprintf(stderr, "\n");
		ret = 1;
	}

	it_config_free(cfg);

	return (ret);
}

static int
delete_tpg(char *tpg, boolean_t force)
{
	int		ret;
	it_config_t	*cfg;
	it_tpg_t	*ptpg = NULL;
	char		*sec = "solaris.smf.modify.stmf";

	ITADM_CHKAUTH(sec);

	if (!tpg) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no target portal group specified"));
		return (EINVAL);
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ptpg = cfg->config_tpg_list;
	for (; ptpg != NULL; ptpg = ptpg->tpg_next) {
		if (strcmp(tpg, ptpg->tpg_name) == 0) {
			break;
		}
	}

	if (!ptpg) {
		(void) fprintf(stderr,
		    gettext("Target portal group %s does not exist"),
		    tpg);
		(void) fprintf(stderr, "\n");
		ret = 1;
	} else {
		ret = it_tpg_delete(cfg, ptpg, force);
		if (ret == EBUSY) {
			(void) fprintf(stderr, "%s\n",
			    gettext(
			    "Target portal group associated with one or more "
			    "targets.  Cannot delete."));
		} else if (ret != 0) {
			output_config_error(ret, gettext("Could not delete "
			    "target portal group"));
		}

		if (ret == 0) {
			ret = it_config_commit(cfg);
			STMF_STALE(ret);
		}
	}

	it_config_free(cfg);

	return (ret);
}

static int
modify_initiator(char *ini, nvlist_t *proplist, boolean_t create)
{
	int		ret;
	it_config_t	*cfg;
	it_ini_t	*inip;
	nvlist_t	*errlist = NULL;
	nvpair_t	*nvp = NULL;
	char		*sec = "solaris.smf.modify.stmf";
	boolean_t	changed = B_TRUE;

	ITADM_CHKAUTH(sec);

	if (!ini) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no initiator specified"));
		return (EINVAL);
	} else if (create) {
		/*
		 * validate input name - what are the rules for EUI
		 * and IQN values?
		 */
		if (!IS_IQN_NAME(ini) && !IS_EUI_NAME(ini)) {
			(void) fprintf(stderr, gettext("Invalid name %s"),
			    ini);
			(void) fprintf(stderr, "\n");
			return (EINVAL);
		}
	}

	/*
	 * See if any properties were actually specified.
	 */
	if (proplist) {
		nvp = nvlist_next_nvpair(proplist, nvp);
	}

	if ((nvp == NULL) && !create) {
		changed = B_FALSE;
	}

	/*
	 * If no properties, and this is really a modify op, verify
	 * that the requested initiator exists, but then don't do anything.
	 * Modifying non-existent is an error; doing nothing to a defined
	 * initiator is not.
	 */

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

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

		inip = inip->ini_next;
	}

	if (create) {
		if (inip) {
			(void) fprintf(stderr,
			    gettext("Initiator %s already exists"),
			    inip->ini_name);
			(void) fprintf(stderr, "\n");
			ret = EINVAL;
		} else {
			ret = it_ini_create(cfg, &inip, ini);
			if (ret != 0) {
				if (ret == EFAULT) {
					(void) fprintf(stderr,
					    gettext("Invalid iSCSI name %s"),
					    ini);
					(void) fprintf(stderr, "\n");
				} else {
					output_config_error(ret, gettext(
					    "Error creating initiator"));
				}
			}
		}
	} else if (!inip) {
		ret = ENOENT;
		(void) fprintf(stderr,
		    gettext("Error, initiator %s not found"),
		    ini);
		(void) fprintf(stderr, "\n");
	}

	if ((ret == 0) && nvp) {
		ret = it_ini_setprop(inip, proplist, &errlist);

		if (ret != 0) {
			(void) fprintf(stderr,
			    gettext("Error setting initiator properties: %d"),
			    ret);
			(void) fprintf(stderr, "\n");
			if (errlist) {
				nvpair_t	*nvp = NULL;
				char		*nn;
				char		*nv;

				while ((nvp = nvlist_next_nvpair(errlist, nvp))
				    != NULL) {
					nv = NULL;

					nn = nvpair_name(nvp);
					(void) nvpair_value_string(nvp, &nv);

					if (nv != NULL) {
						(void) fprintf(stderr,
						    "\t%s: %s\n", nn, nv);
					}
				}

				nvlist_free(errlist);
			}
		}
	}

	if ((ret == 0) && changed) {
		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	}

	it_config_free(cfg);

	return (ret);
}

static int
list_initiator(char *ini, boolean_t verbose, boolean_t script) /* ARGSUSED */
{
	int		ret;
	it_config_t	*cfg;
	it_ini_t	*ptr;
	boolean_t	found = B_FALSE;
	boolean_t	first = B_TRUE;
	char		*isecret;
	char		*iuser;
	char		*sec = "solaris.smf.read.stmf";

	ITADM_CHKAUTH(sec);

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ptr = cfg->config_ini_list;

	for (; ptr != NULL; ptr = ptr->ini_next) {
		isecret = "unset";
		iuser = "<none>";

		if (found) {
			break;
		}

		if (ini) {
			if (strcasecmp(ini, ptr->ini_name) != 0) {
				continue;
			} else {
				found = B_TRUE;
			}
		}

		if (ptr->ini_properties) {
			if (nvlist_exists(ptr->ini_properties, "chapsecret")) {
				isecret = "set";
			}
			(void) nvlist_lookup_string(ptr->ini_properties,
			    "chapuser", &iuser);

		}

		/* there's nothing to print for verbose yet */
		if (!script && first) {
			(void) printf("%-61s%-10s%-7s\n", "INITIATOR NAME",
			    "CHAPUSER", "SECRET");
			first = B_FALSE;
		}

		if (!script) {
			/*
			 * try not to let columns run into each other.
			 * Stick a tab after too-long fields.
			 * Lengths chosen are for the 'common' cases.
			 */
			(void) printf("%-61s", ptr->ini_name);

			if (strlen(ptr->ini_name) > 60) {
				(void) printf("\t");
			}

			(void) printf("%-15s", iuser);
			if (strlen(iuser) >= 15) {
				(void) printf("\t");
			}

			(void) printf("%-4s", isecret);
		} else {
			(void) printf("%s\t%s\t%s", ptr->ini_name,
			    iuser, isecret);
		}

		(void) printf("\n");
	}

	if (ini && (!found)) {
		(void) fprintf(stderr,
		    gettext("Initiator %s not found!"), ini);
		(void) fprintf(stderr, "\n");
		ret = 1;
	}

	it_config_free(cfg);

	return (ret);
}

int
delete_initiator(char *ini)
{
	int		ret;
	it_config_t	*cfg;
	it_ini_t	*ptr;
	char		*sec = "solaris.smf.modify.stmf";

	ITADM_CHKAUTH(sec);

	if (!ini) {
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no initiator specified"));
		return (EINVAL);
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

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

		ptr = ptr->ini_next;
	}

	if (ptr) {
		it_ini_delete(cfg, ptr);

		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	} else {
		(void) fprintf(stderr,
		    gettext("Initiator %s not found"), ini);
		(void) fprintf(stderr, "\n");
		ret = 1;
	}

	return (ret);
}

static int
modify_defaults(nvlist_t *proplist)
{
	int		ret;
	it_config_t	*cfg;
	nvlist_t	*errlist = NULL;
	nvpair_t	*nvp = NULL;
	char		*sec = "solaris.smf.modify.stmf";

	ITADM_CHKAUTH(sec);

	if (proplist) {
		/* make sure at least one property is specified */
		nvp = nvlist_next_nvpair(proplist, nvp);
	}

	if (nvp == NULL) {
		/* empty list */
		(void) fprintf(stderr, "%s\n",
		    gettext("Error, no properties specified"));
		return (EINVAL);
	}

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	ret = it_config_setprop(cfg, proplist, &errlist);
	if (ret != 0) {
		(void) fprintf(stderr,
		    gettext("Error setting global properties: %d"),
		    ret);
		(void) fprintf(stderr, "\n");
		if (errlist) {
			nvpair_t	*nvp = NULL;
			char		*nn;
			char		*nv;

			while ((nvp = nvlist_next_nvpair(errlist, nvp))
			    != NULL) {
				nv = NULL;

				nn = nvpair_name(nvp);
				(void) nvpair_value_string(nvp, &nv);

				if (nv != NULL) {
					(void) fprintf(stderr, "\t%s: %s\n",
					    nn, nv);
				}
			}

			nvlist_free(errlist);
		}
	}

	if (ret == 0) {
		ret = it_config_commit(cfg);
		STMF_STALE(ret);
	}

	it_config_free(cfg);

	return (ret);
}

static int
list_defaults(boolean_t script)
{
	int		ret;
	it_config_t	*cfg;
	nvlist_t	*nvl;
	char		*alias = "<none>";
	char		*auth = "<none>";
	char		*isns = "disabled";
	char		**isvrs = NULL;
	uint32_t	scount = 0;
	char		*rsvr = "<none>";
	char		*rsecret = "unset";
	boolean_t	val = B_FALSE;
	int		i;
	char		*sec = "solaris.smf.read.stmf";

	ITADM_CHKAUTH(sec);

	ret = it_config_load(&cfg);
	if (ret != 0) {
		output_config_error(ret,
		    gettext("Error retrieving iSCSI target configuration"));
		return (ret);
	}

	nvl = cfg->config_global_properties;

	/* look up all possible options */
	(void) nvlist_lookup_string(nvl, "alias", &alias);
	(void) nvlist_lookup_string(nvl, "auth", &auth);
	(void) nvlist_lookup_boolean_value(nvl, "isns", &val);
	if (val == B_TRUE) {
		isns = "enabled";
	}
	(void) nvlist_lookup_string_array(nvl, "isnsserver", &isvrs,
	    &scount);
	(void) nvlist_lookup_string(nvl, "radiusserver", &rsvr);
	if (nvlist_exists(nvl, "radiussecret")) {
		rsecret = "set";
	}

	if (!script) {
		(void) printf("%s:\n\n",
		    gettext("iSCSI Target Default Properties"));
	}

	if (script) {
		(void) printf("%s\t%s\t%s\t%s\t%s\t",
		    alias, auth, rsvr, rsecret, isns);
	} else {
		(void) printf("%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n"
		    "%-15s\t%s\n%-15s\t",
		    "alias:", alias, "auth:", auth, "radiusserver:",
		    rsvr, "radiussecret:", rsecret, "isns:", isns,
		    "isnsserver:");
	}

	for (i = 0; i < scount; i++) {
		if (!isvrs || !isvrs[i]) {
			break;
		}
		if (i > 0) {
			(void) printf(",");
		}
		(void) printf("%s", isvrs[i]);
	}

	if (i == 0) {
		(void) printf("%s", "<none>");
	}

	(void) printf("\n");

	it_config_free(cfg);

	return (0);
}

static int
itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
    char *phrase)
{
	int		ret = 0;
	char		*pass;
	char		buf[1024];
	int		fd;
	struct stat64	sbuf;
	size_t		rd;

	if (!nvl || !key) {
		return (EINVAL);
	}

	if (passfile) {
		ret = stat64(passfile, &sbuf);
		if ((ret != 0) || (!S_ISREG(sbuf.st_mode))) {
			(void) fprintf(stderr,
			    gettext("Invalid secret file %s"),
			    passfile);
			(void) fprintf(stderr, "\n");
			return (EBADF);
		}

		fd = open64(passfile, O_RDONLY);
		if (fd == -1) {
			ret = errno;
			(void) fprintf(stderr,
			    gettext("Could not open secret file %s: "),
			    passfile);
			output_config_error(ret, NULL);
			return (ret);
		}

		rd = read(fd, buf, sbuf.st_size);
		(void) close(fd);

		if (rd != sbuf.st_size) {
			ret = EIO;
			(void) fprintf(stderr,
			    gettext("Could not read secret file %s: "),
			    passfile);
			output_config_error(ret, NULL);
			return (ret);
		}

		/* ensure buf is properly terminated */
		buf[rd] = '\0';

		/* if last char is a newline, strip it off */
		if (buf[rd - 1] == '\n') {
			buf[rd - 1] = '\0';
		}

		/* validate length */
		if ((strlen(buf) > 255) || (strlen(buf) < 12)) {
			(void) fprintf(stderr, "%s\n",
			    gettext(
			    "Secret must be between 12 and 255 characters"));
			return (EINVAL);
		}
	} else {
		/* prompt for secret */
		if (!phrase) {
			return (EINVAL);
		}

		pass = getpassphrase(phrase);
		if (!pass) {
			ret = errno;
			output_config_error(ret,
			    gettext("Could not read secret"));
			return (ret);
		}

		/* validate length */
		if ((strlen(pass) > 255) || (strlen(pass) < 12)) {
			(void) fprintf(stderr, "%s\n",
			    gettext(
			    "Secret must be between 12 and 255 characters"));
			return (EINVAL);
		}

		(void) strlcpy(buf, pass, sizeof (buf));

		/* confirm entered secret */
		pass = getpassphrase(gettext("Re-enter secret: "));
		if (!pass) {
			ret = errno;
			output_config_error(ret,
			    gettext("Could not read secret"));
			return (ret);
		}

		if (strcmp(buf, pass) != 0) {
			ret = EINVAL;
			(void) fprintf(stderr, "%s\n",
			    gettext("Secret validation failed"));
			return (ret);
		}

	}

	ret = nvlist_add_string(nvl, key, buf);

	return (ret);
}

static int
itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num)
{
	int		count;
	char		*bufp;
	char		**arr;

	if (!opt || !key || !nvl) {
		return (EINVAL);
	}

	bufp = opt;
	count = 1;

	for (;;) {
		bufp = strchr(bufp, ',');
		if (!bufp) {
			break;
		}
		bufp++;
		count++;
	}

	arr = calloc(count, sizeof (char *));
	if (!arr) {
		return (ENOMEM);
	}

	bufp = opt;
	/* set delimiter to comma */
	(void) bufsplit(",", 0, NULL);

	/* split up that buf! */
	(void) bufsplit(bufp, count, arr);

	/* if requested, return the number of array members found */
	if (num) {
		*num = count;
	}

	return (nvlist_add_string_array(nvl, key, arr, count));
}

static void
tag_name_to_num(char *tagname, uint16_t *tagnum)
{
	ulong_t		id;
	char		*ptr = NULL;

	if (!tagname || !tagnum) {
		return;
	}

	*tagnum = 0;

	id = strtoul(tagname, &ptr, 10);

	/* Must be entirely numeric and in-range */
	if (ptr && (*ptr != '\0')) {
		return;
	}

	if ((id <= UINT16_MAX) && (id > 1)) {
		*tagnum = (uint16_t)id;
	}
}

/*
 * Print error messages to stderr for errnos and expected stmf errors.
 * This function should generally not be used for cases where the
 * calling code can generate a more detailed error message based on
 * the contextual knowledge of the meaning of specific errors.
 */
static void
output_config_error(int error, char *msg)
{

	if (msg) {
		(void) fprintf(stderr, "%s: ", msg);
	}

	if (error & STMF_STATUS_ERROR) {
		switch (error) {
		case STMF_ERROR_PERM:
			(void) fprintf(stderr, "%s",
			    gettext("permission denied"));
			break;
		case STMF_ERROR_BUSY:
			(void) fprintf(stderr, "%s",
			    gettext("resource busy"));
			break;
		case STMF_ERROR_NOMEM:
			(void) fprintf(stderr, "%s",
			    gettext("out of memory"));
			break;
		case STMF_ERROR_SERVICE_NOT_FOUND:
			(void) fprintf(stderr, "%s",
			    gettext("STMF service not found"));
			break;
		case STMF_ERROR_SERVICE_DATA_VERSION:
			(void) fprintf(stderr, "%s",
			    gettext("STMF service version incorrect"));
			break;
		case STMF_ERROR_PROV_DATA_STALE:
			(void) fprintf(stderr, "%s",
			    gettext("Configuration changed during processing. "
			    "Check the configuration, then retry this "
			    "command if appropriate."));
			break;
		default:
			(void) fprintf(stderr, "%s", gettext("unknown error"));
			break;
		}
	} else {
		char buf[80] = "";

		(void) strerror_r(error, buf, sizeof (buf));
		(void) fprintf(stderr, "%s", buf);
	}

	(void) fprintf(stderr, "\n");
}