xref: /illumos-gate/usr/src/cmd/itadm/itadm.c (revision ef150c2b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
25  */
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <getopt.h>
35 #include <strings.h>
36 #include <ctype.h>
37 #include <libnvpair.h>
38 #include <libintl.h>
39 #include <libgen.h>
40 #include <pwd.h>
41 #include <auth_attr.h>
42 #include <secdb.h>
43 #include <libscf.h>
44 #include <limits.h>
45 #include <locale.h>
46 
47 #include <libstmf.h>
48 #include <libiscsit.h>
49 
50 /* what's this used for?? */
51 #define	ITADM_VERSION	"1.0"
52 
53 /* SMF service info */
54 #define	ISCSIT_SVC	"svc:/network/iscsi/target:default"
55 
56 #define	STMF_STALE(ret) {\
57 	if (ret == STMF_ERROR_PROV_DATA_STALE) {\
58 		output_config_error(ret, NULL);\
59 	} else if (ret != 0) {\
60 		output_config_error(ret,\
61 		    gettext("Configuration change failed"));\
62 	}\
63 }
64 
65 #define	ITADM_CHKAUTH(sec) {\
66 	if (!chkauthattr(sec, itadm_uname)) {\
67 		(void) fprintf(stderr,\
68 		    gettext("Error, operation requires authorization %s"),\
69 		    sec);\
70 		(void) fprintf(stderr, "\n");\
71 		return (1);\
72 	}\
73 }
74 
75 
76 static struct option itadm_long[] = {
77 	{"alias",		required_argument,	NULL, 'l'},
78 	{"auth-method",		required_argument,	NULL, 'a'},
79 	{"chap-secret",		no_argument,		NULL, 's'},
80 	{"chap-secret-file",	required_argument,	NULL, 'S'},
81 	{"chap-user",		required_argument,	NULL, 'u'},
82 	{"force",		no_argument,		NULL, 'f'},
83 	{"help",		no_argument,		NULL, 'h'},
84 	{"help",		no_argument,		NULL, '?'},
85 	{"isns",		required_argument,	NULL, 'i'},
86 	{"isns-server",		required_argument,	NULL, 'I'},
87 	{"node-name",		required_argument,	NULL, 'n'},
88 	{"parsable",		no_argument,		NULL, 'p'},
89 	{"radius-secret",	no_argument,		NULL, 'd'},
90 	{"radius-secret-file",	required_argument,	NULL, 'D'},
91 	{"radius-server",	required_argument,	NULL, 'r'},
92 	{"tpg-tag",		required_argument,	NULL, 't'},
93 	{"verbose",		no_argument,		NULL, 'v'},
94 	{"version",		no_argument,		NULL, 'V'},
95 	{NULL, 0, NULL, 0}
96 };
97 
98 char c_tgt[] =
99 "	create-target	[-a radius|chap|none|default] [-s]\n"
100 "			[-S <chap-secret-path>] [-u <chap-user-name>]\n"
101 "			[-n <target-node-name>] [-l <alias>]\n"
102 "			[-t <tpg-name>[,<tpg-name>]...]";
103 
104 static char m_tgt[] =
105 "	modify-target	[-a radius|chap|none|default] [-s]\n"
106 "			[-S <chap-secret-path>] [-u <chap-username>]\n"
107 "			[-n <new-target-node-name>] [-l <alias>]\n"
108 "			[-t <tpg-name>[,<tpg-name>]...] <target-node-name>";
109 
110 static char d_tgt[] =
111 "	delete-target	[-f] <target-node-name>";
112 
113 static char l_tgt[] =
114 "	list-target	[-pv] [<target-node-name>]";
115 
116 static char c_tpg[] =
117 "	create-tpg	<tpg-name> <IP-address>[:<port>]...";
118 
119 static char l_tpg[] =
120 "	list-tpg	[-pv] [<tpg-name>]";
121 
122 static char d_tpg[] =
123 "	delete-tpg	[-f] <tpg-name>";
124 
125 static char c_ini[] =
126 "	create-initiator [-s] [-S <chap-secret-path>]\n"
127 "			[-u <chap-username>] <initiator-node-name>";
128 
129 static char m_ini[] =
130 "	modify-initiator [-s] [-S <chap-secret-path>]\n"
131 "			[-u <chap-username>] <initiator-node-name>";
132 
133 static char l_ini[] =
134 "	list-initiator	[-pv] [<initiator-node-name>]";
135 
136 static char d_ini[] =
137 "	delete-initiator <initiator-node-name>";
138 
139 static char m_def[] =
140 "	modify-defaults	[-a radius|chap|none] [-r <IP-address>[:<port>]] [-d]\n"
141 "			[-D <radius-secret-path>] [-i enable|disable]\n"
142 "			[-I <IP-address>[:<port>][,<IP-adddress>[:<port>]]...]";
143 
144 static char l_def[] =
145 "	list-defaults	[-p]";
146 
147 
148 /* keep the order of this enum in the same order as the 'subcmds' struct */
149 typedef enum {
150 	CREATE_TGT,
151 	MODIFY_TGT,
152 	DELETE_TGT,
153 	LIST_TGT,
154 	CREATE_TPG,
155 	DELETE_TPG,
156 	LIST_TPG,
157 	CREATE_INI,
158 	MODIFY_INI,
159 	LIST_INI,
160 	DELETE_INI,
161 	MODIFY_DEF,
162 	LIST_DEF,
163 	NULL_SUBCMD	/* must always be last! */
164 } itadm_sub_t;
165 
166 typedef struct {
167 	char		*name;
168 	char		*shortopts;
169 	char		*usemsg;
170 } itadm_subcmds_t;
171 
172 static itadm_subcmds_t	subcmds[] = {
173 	{"create-target", ":a:sS:u:n:l:t:h?", c_tgt},
174 	{"modify-target", ":a:sS:u:n:l:t:h?", m_tgt},
175 	{"delete-target", ":fh?", d_tgt},
176 	{"list-target", ":hpv?", l_tgt},
177 	{"create-tpg", ":h?", c_tpg},
178 	{"delete-tpg", ":fh?", d_tpg},
179 	{"list-tpg", ":hpv?", l_tpg},
180 	{"create-initiator", ":sS:u:h?", c_ini},
181 	{"modify-initiator", ":sS:u:h?", m_ini},
182 	{"list-initiator", ":hpv?", l_ini},
183 	{"delete-initiator", ":h?", d_ini},
184 	{"modify-defaults", ":a:r:dD:i:I:h?", m_def},
185 	{"list-defaults", ":hp?", l_def},
186 	{NULL, ":h?", NULL},
187 };
188 
189 /* used for checking if user is authorized */
190 static char *itadm_uname = NULL;
191 
192 /* prototypes */
193 static int
194 itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
195     char *phrase);
196 
197 static int
198 itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num);
199 
200 static int
201 create_target(char *tgt, nvlist_t *proplist);
202 
203 static int
204 modify_target(char *tgt, char *new, nvlist_t *proplist);
205 
206 static int
207 delete_target(char *tgt, boolean_t force);
208 
209 static int
210 list_target(char *tgt, boolean_t verbose, boolean_t script);
211 
212 static int
213 create_tpg(char *tpg, int addrc, char **addrs);
214 
215 static int
216 list_tpg(char *tpg, boolean_t verbose, boolean_t script);
217 
218 static int
219 delete_tpg(char *tpg, boolean_t force);
220 
221 static int
222 modify_initiator(char *ini, nvlist_t *proplist, boolean_t create);
223 
224 static int
225 list_initiator(char *ini, boolean_t verbose, boolean_t script);
226 
227 static int
228 delete_initiator(char *ini);
229 
230 static int
231 modify_defaults(nvlist_t *proplist);
232 
233 static int
234 list_defaults(boolean_t script);
235 
236 static void
237 tag_name_to_num(char *tagname, uint16_t *tagnum);
238 
239 /* prototype from iscsit_common.h */
240 extern int
241 sockaddr_to_str(struct sockaddr_storage *sa, char **addr);
242 
243 static void output_config_error(int error_code, char *msg);
244 
245 int
main(int argc,char * argv[])246 main(int argc, char *argv[])
247 {
248 	int		ret = 0;
249 	int		idx = NULL_SUBCMD;
250 	int		c;
251 	int		newargc = argc;
252 	char		**newargv = NULL;
253 	char		*objp;
254 	int		itind = 0;
255 	nvlist_t	*proplist = NULL;
256 	boolean_t	verbose = B_FALSE;
257 	boolean_t	script = B_FALSE;
258 	boolean_t	tbool;
259 	char		*targetname = NULL;
260 	char		*propname;
261 	boolean_t	force = B_FALSE;
262 	struct passwd	*pwd = NULL;
263 	uint32_t	count = 0;
264 	char		*smfstate = NULL;
265 
266 	(void) setlocale(LC_ALL, "");
267 	(void) textdomain(TEXT_DOMAIN);
268 
269 	if (argc < 2) {
270 		ret = 1;
271 		goto usage_error;
272 	}
273 
274 	for (idx = 0; subcmds[idx].name != NULL; idx++) {
275 		if (strcmp(argv[1], subcmds[idx].name) == 0) {
276 			break;
277 		}
278 	}
279 
280 
281 	/* get the caller's user name for subsequent chkauthattr() calls */
282 	pwd = getpwuid(getuid());
283 	if (pwd == NULL) {
284 		(void) fprintf(stderr, "%s\n",
285 		    gettext("Could not determine callers user name"));
286 		return (1);
287 	}
288 
289 	itadm_uname = strdup(pwd->pw_name);
290 
291 	/* increment past command & subcommand */
292 	newargc--;
293 	newargv = &(argv[1]);
294 
295 	ret = nvlist_alloc(&proplist, NV_UNIQUE_NAME, 0);
296 	if (ret != 0) {
297 		ret = errno;
298 		output_config_error(ret, gettext("Could not allocate nvlist"));
299 		ret = 1;
300 		goto usage_error;
301 	}
302 
303 	while ((ret == 0) && (newargv)) {
304 		c = getopt_long(newargc, newargv, subcmds[idx].shortopts,
305 		    itadm_long, &itind);
306 		if (c == -1) {
307 			break;
308 		}
309 
310 		switch (c) {
311 			case 0:
312 				/* flag set by getopt */
313 				break;
314 			case 'a':
315 				ret = nvlist_add_string(proplist,
316 				    "auth", optarg);
317 				break;
318 			case 'd':
319 				ret = itadm_get_password(proplist,
320 				    "radiussecret", NULL,
321 				    gettext("Enter RADIUS secret: "));
322 				break;
323 			case 'D':
324 				ret = itadm_get_password(proplist,
325 				    "radiussecret", optarg, NULL);
326 				break;
327 			case 'f':
328 				force = B_TRUE;
329 				break;
330 			case '?':
331 				/*
332 				 * '?' is returned for both unrecognized
333 				 * options and if explicitly provided on
334 				 * the command line.  The latter should
335 				 * be handled the same as -h.
336 				 */
337 				if (strcmp(newargv[optind-1], "-?") != 0) {
338 					(void) fprintf(stderr,
339 					    gettext("Unrecognized option %s"),
340 					    newargv[optind-1]);
341 					(void) fprintf(stderr, "\n");
342 					ret = 1;
343 				}
344 				goto usage_error;
345 			case 'h':
346 				goto usage_error;
347 			case 'i':
348 				if (strncmp(optarg, "enable", strlen(optarg))
349 				    == 0) {
350 					tbool = B_TRUE;
351 				} else if (strncmp(optarg, "disable",
352 				    strlen(optarg)) == 0) {
353 					tbool = B_FALSE;
354 				} else {
355 					(void) fprintf(stderr, "%s\n",
356 					    gettext("invalid value for -i"));
357 					ret = 1;
358 					break;
359 				}
360 				ret = nvlist_add_boolean_value(proplist,
361 				    "isns", tbool);
362 				break;
363 			case 'I':
364 				/* possibly multi-valued */
365 				ret = itadm_opt_to_arr(proplist,
366 				    "isnsserver", optarg, &count);
367 				if ((ret == 0) && (count > 8)) {
368 					(void) fprintf(stderr, "%s\n",
369 					    gettext(
370 					    "Too many iSNS servers specified, "
371 					    "maximum of 8 allowed"));
372 					ret = 1;
373 				}
374 				break;
375 			case 'l':
376 				ret = nvlist_add_string(proplist,
377 				    "alias", optarg);
378 				break;
379 			case 'n':
380 				targetname = strdup(optarg);
381 				if (targetname == NULL) {
382 					ret = ENOMEM;
383 				}
384 				break;
385 			case 'p':
386 				script = B_TRUE;
387 				break;
388 			case 'r':
389 				ret = nvlist_add_string(proplist,
390 				    "radiusserver", optarg);
391 				break;
392 			case 's':
393 				if ((idx == CREATE_TGT) ||
394 				    (idx == MODIFY_TGT)) {
395 					propname = "targetchapsecret";
396 				} else {
397 					propname = "chapsecret";
398 				}
399 				ret = itadm_get_password(proplist,
400 				    propname, NULL,
401 				    gettext("Enter CHAP secret: "));
402 				break;
403 			case 'S':
404 				if ((idx == CREATE_TGT) ||
405 				    (idx == MODIFY_TGT)) {
406 					propname = "targetchapsecret";
407 				} else {
408 					propname = "chapsecret";
409 				}
410 				ret = itadm_get_password(proplist,
411 				    propname, optarg, NULL);
412 				break;
413 			case 't':
414 				/* possibly multi-valued */
415 				ret = itadm_opt_to_arr(proplist,
416 				    "tpg-tag", optarg, NULL);
417 				break;
418 			case 'u':
419 				if ((idx == CREATE_TGT) ||
420 				    (idx == MODIFY_TGT)) {
421 					propname = "targetchapuser";
422 				} else {
423 					propname = "chapuser";
424 				}
425 				ret = nvlist_add_string(proplist,
426 				    propname, optarg);
427 				break;
428 			case 'v':
429 				verbose = B_TRUE;
430 				break;
431 			case ':':
432 				(void) fprintf(stderr,
433 				    gettext("Option %s requires an operand"),
434 				    newargv[optind-1]);
435 				(void) fprintf(stderr, "\n");
436 
437 				/* FALLTHROUGH */
438 			default:
439 				ret = 1;
440 				break;
441 		}
442 	}
443 
444 	if (ret != 0) {
445 		goto usage_error;
446 	}
447 
448 	/* after getopt() to allow handling of -h option */
449 	if ((itadm_sub_t)idx == NULL_SUBCMD) {
450 		(void) fprintf(stderr, "%s\n",
451 		    gettext("Error, no subcommand specified"));
452 		ret = 1;
453 		goto usage_error;
454 	}
455 
456 	/*
457 	 * some subcommands take multiple operands, so adjust now that
458 	 * getopt is complete
459 	 */
460 	newargc -= optind;
461 	if (newargc == 0) {
462 		newargv = NULL;
463 		objp = NULL;
464 	} else {
465 		newargv = &(newargv[optind]);
466 		objp = newargv[0];
467 	}
468 
469 	if (objp == NULL) {
470 		switch ((itadm_sub_t)idx) {
471 		case MODIFY_TGT:
472 		case DELETE_TGT:
473 		case CREATE_TPG:
474 		case DELETE_TPG:
475 		case CREATE_INI:
476 		case MODIFY_INI:
477 		case DELETE_INI:
478 			/* These subcommands need at least one operand */
479 			(void) fprintf(stderr,
480 			    gettext("Error, %s requires an operand"),
481 			    subcmds[idx].name);
482 			(void) fprintf(stderr, "\n");
483 
484 			ret = 1;
485 			goto usage_error;
486 		default:
487 			break;
488 		}
489 	}
490 
491 	if (newargc > 1) {
492 		switch ((itadm_sub_t)idx) {
493 		case MODIFY_TGT:
494 		case DELETE_TGT:
495 		case LIST_TGT:
496 		case DELETE_TPG:
497 		case LIST_TPG:
498 		case CREATE_INI:
499 		case MODIFY_INI:
500 		case LIST_INI:
501 		case DELETE_INI:
502 			/* These subcommands should have at most one operand */
503 			(void) fprintf(stderr,
504 			    gettext("Error, %s accepts only a single operand"),
505 			    subcmds[idx].name);
506 			(void) fprintf(stderr, "\n");
507 
508 			ret = 1;
509 			goto usage_error;
510 
511 		default:
512 			break;
513 		}
514 	}
515 
516 	if (newargc > 0) {
517 		switch ((itadm_sub_t)idx) {
518 		case CREATE_TGT:
519 		case MODIFY_DEF:
520 		case LIST_DEF:
521 			/* These subcommands do not support an operand */
522 			(void) fprintf(stderr,
523 			    gettext("Error, %s does not support any operands"),
524 			    subcmds[idx].name);
525 			(void) fprintf(stderr, "\n");
526 
527 			ret = 1;
528 			goto usage_error;
529 
530 		default:
531 			break;
532 		}
533 	}
534 
535 	/*
536 	 * XXX - this should probably get pushed down to the library
537 	 * depending on the decision to allow/disallow configuratoin
538 	 * without the service running.
539 	 */
540 	/*
541 	 * Make sure iSCSI target service is enabled before
542 	 * proceeding.
543 	 */
544 	smfstate = smf_get_state(ISCSIT_SVC);
545 	if (!smfstate ||
546 	    (strcmp(smfstate, SCF_STATE_STRING_ONLINE) != 0)) {
547 		(void) fprintf(stderr, "%s\n",
548 		    gettext("The iSCSI target service must be online "
549 		    "before running this command."));
550 		(void) fprintf(stderr,
551 		    gettext("Use 'svcadm enable -r %s'"), ISCSIT_SVC);
552 		(void) fprintf(stderr, "\n");
553 		(void) fprintf(stderr, "%s\n",
554 		    gettext("to enable the service and its prerequisite "
555 		    "services and/or"));
556 		(void) fprintf(stderr,
557 		    gettext("'svcs -x %s' to determine why it is not online."),
558 		    ISCSIT_SVC);
559 		(void) fprintf(stderr, "\n");
560 
561 		return (1);
562 	}
563 
564 	switch ((itadm_sub_t)idx) {
565 		case CREATE_TGT:
566 			/*
567 			 * OK for targetname to be NULL here.  If the
568 			 * user did not specify a target name,
569 			 * one will be generated.
570 			 */
571 			ret = create_target(targetname, proplist);
572 			break;
573 		case MODIFY_TGT:
574 			ret = modify_target(objp, targetname, proplist);
575 			break;
576 		case DELETE_TGT:
577 			ret = delete_target(objp, force);
578 			break;
579 		case LIST_TGT:
580 			ret = list_target(objp, verbose, script);
581 			break;
582 		case CREATE_TPG:
583 			ret = create_tpg(objp, newargc - 1, &(newargv[1]));
584 			break;
585 		case DELETE_TPG:
586 			ret = delete_tpg(objp, force);
587 			break;
588 		case LIST_TPG:
589 			ret = list_tpg(objp, verbose, script);
590 			break;
591 		case CREATE_INI:
592 			ret = modify_initiator(objp, proplist, B_TRUE);
593 			break;
594 		case MODIFY_INI:
595 			ret = modify_initiator(objp, proplist, B_FALSE);
596 			break;
597 		case LIST_INI:
598 			ret = list_initiator(objp, verbose, script);
599 			break;
600 		case DELETE_INI:
601 			ret = delete_initiator(objp);
602 			break;
603 		case MODIFY_DEF:
604 			ret = modify_defaults(proplist);
605 			break;
606 		case LIST_DEF:
607 			ret = list_defaults(script);
608 			break;
609 		default:
610 			ret = 1;
611 			goto usage_error;
612 	}
613 
614 	if (ret != 0) {
615 		(void) fprintf(stderr,
616 		    gettext("itadm %s failed with error %d"),
617 		    subcmds[idx].name, ret);
618 		(void) fprintf(stderr, "\n");
619 	}
620 	return (ret);
621 
622 usage_error:
623 	if (subcmds[idx].name) {
624 		(void) printf("%s\n%s\n", gettext("usage:"),
625 		    gettext(subcmds[idx].usemsg));
626 	} else {
627 		/* overall usage */
628 		(void) printf("%s\n",
629 		    gettext("usage: itadm <subcommand> <args> ..."));
630 		for (idx = 0; subcmds[idx].name != NULL; idx++) {
631 			if (!subcmds[idx].usemsg) {
632 				continue;
633 			}
634 			(void) printf("%s\n", gettext(subcmds[idx].usemsg));
635 		}
636 	}
637 
638 	return (ret);
639 }
640 
641 static int
create_target(char * tgt,nvlist_t * proplist)642 create_target(char *tgt, nvlist_t *proplist)
643 {
644 	int		ret;
645 	it_config_t	*cfg = NULL;
646 	it_tgt_t	*tgtp;
647 	char		**tags = NULL;
648 	uint32_t	count = 0;
649 	nvlist_t	*errlist = NULL;
650 	int		i;
651 	it_tpg_t	*tpg = NULL;
652 	uint16_t	tagid = 0;
653 	it_tpgt_t	*tpgt;
654 	char		*sec = "solaris.smf.modify.stmf";
655 	boolean_t	did_it_config_load = B_FALSE;
656 
657 	ITADM_CHKAUTH(sec);
658 
659 	if (tgt) {
660 		/*
661 		 * Validate target name.
662 		 */
663 		if (!IS_IQN_NAME(tgt) && !IS_EUI_NAME(tgt)) {
664 			(void) fprintf(stderr, gettext("Invalid name %s"),
665 			    tgt);
666 			(void) fprintf(stderr, "\n");
667 			return (EINVAL);
668 		}
669 	}
670 
671 	ret = it_config_load(&cfg);
672 	if (ret != 0) {
673 		output_config_error(ret,
674 		    gettext("Error retrieving iSCSI target configuration"));
675 		goto done;
676 	}
677 
678 	did_it_config_load = B_TRUE;
679 
680 	ret = it_tgt_create(cfg, &tgtp, tgt);
681 	if (ret != 0) {
682 		if (ret == EFAULT) {
683 			(void) fprintf(stderr,
684 			    gettext("Invalid iSCSI name %s"), tgt);
685 			(void) fprintf(stderr, "\n");
686 		} else if (ret == EEXIST) {
687 			(void) fprintf(stderr,
688 			    gettext("iSCSI target %s already configured"),
689 			    tgt);
690 			(void) fprintf(stderr, "\n");
691 		} else if (ret == E2BIG) {
692 			(void) fprintf(stderr,
693 			    gettext("Maximum of %d iSCSI targets"),
694 			    MAX_TARGETS);
695 			(void) fprintf(stderr, "\n");
696 		} else {
697 			output_config_error(ret,
698 			    gettext("Error creating target"));
699 		}
700 
701 		goto done;
702 	}
703 
704 	/* set the target portal group tags */
705 	ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
706 	    &count);
707 
708 	if (ret == ENOENT) {
709 		/* none specified.  is this ok? */
710 		ret = 0;
711 	} else if (ret != 0) {
712 		output_config_error(ret, gettext("Internal error"));
713 		goto done;
714 	}
715 
716 	/* special case, don't set any TPGs */
717 	if (tags && (count == 1) && (strcmp("default", tags[0]) == 0)) {
718 		count = 0;
719 	}
720 
721 	for (i = 0; i < count; i++) {
722 		if (!tags[i]) {
723 			continue;
724 		}
725 
726 		/* see that all referenced groups are already defined */
727 		tpg = cfg->config_tpg_list;
728 		while (tpg != NULL) {
729 			if (strcmp(tags[i], tpg->tpg_name) == 0) {
730 				break;
731 			}
732 
733 			tpg = tpg->tpg_next;
734 		}
735 		if (tpg == NULL) {
736 			(void) fprintf(stderr,
737 			    gettext("Invalid tpg-tag %s, tag not defined"),
738 			    tags[i]);
739 			(void) fprintf(stderr, "\n");
740 			ret = 1;
741 			goto done;
742 		}
743 
744 		/* generate the tag number to use */
745 		tag_name_to_num(tags[i], &tagid);
746 
747 		ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
748 		if (ret != 0) {
749 			(void) fprintf(stderr, gettext(
750 			    "Could not add target portal group tag %s: "),
751 			    tags[i]);
752 			output_config_error(ret, NULL);
753 			goto done;
754 		}
755 		tagid++;
756 	}
757 
758 	/* remove the tags from the proplist before continuing */
759 	if (tags) {
760 		(void) nvlist_remove_all(proplist, "tpg-tag");
761 	}
762 
763 	ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
764 	if (ret != 0) {
765 		(void) fprintf(stderr,
766 		    gettext("Error setting target properties: %d"), ret);
767 		(void) fprintf(stderr, "\n");
768 		if (errlist) {
769 			nvpair_t	*nvp = NULL;
770 			char		*nn;
771 			char		*nv;
772 
773 			while ((nvp = nvlist_next_nvpair(errlist, nvp))
774 			    != NULL) {
775 				nv = NULL;
776 
777 				nn = nvpair_name(nvp);
778 				(void) nvpair_value_string(nvp, &nv);
779 
780 				if (nv != NULL) {
781 					(void) fprintf(stderr, "\t%s: %s\n",
782 					    nn, nv);
783 				}
784 			}
785 
786 			nvlist_free(errlist);
787 		}
788 		goto done;
789 	}
790 
791 	if (ret == 0) {
792 		ret = it_config_commit(cfg);
793 		STMF_STALE(ret);
794 	}
795 
796 done:
797 	if (ret == 0) {
798 		(void) printf(gettext("Target %s successfully created"),
799 		    tgtp->tgt_name);
800 		(void) printf("\n");
801 	}
802 
803 	if (did_it_config_load)
804 		it_config_free(cfg);
805 
806 	return (ret);
807 }
808 
809 int
list_target(char * tgt,boolean_t verbose,boolean_t script)810 list_target(char *tgt, boolean_t verbose, boolean_t script)
811 {
812 	int		ret;
813 	it_config_t	*cfg;
814 	it_tgt_t	*ptr;
815 	boolean_t	found = B_FALSE;
816 	boolean_t	first = B_TRUE;
817 	boolean_t	first_tag = B_TRUE;
818 	char		*gauth = "none";
819 	char		*galias = "-";
820 	char		*auth;
821 	char		*alias;
822 	char		*chapu;
823 	char		*chaps;
824 	it_tpgt_t	*tagp;
825 	char		*sec = "solaris.smf.read.stmf";
826 	stmfDevid	devid;
827 	stmfSessionList	*sess = NULL;
828 	stmfTargetProperties	props;
829 	char		*state;
830 	int		num_sessions;
831 
832 	ITADM_CHKAUTH(sec);
833 
834 	ret = it_config_load(&cfg);
835 	if (ret != 0) {
836 		output_config_error(ret,
837 		    gettext("Error retrieving iSCSI target configuration"));
838 		return (ret);
839 	}
840 
841 	ptr = cfg->config_tgt_list;
842 
843 	/* grab global defaults for auth, alias */
844 	if (cfg->config_global_properties) {
845 		(void) nvlist_lookup_string(cfg->config_global_properties,
846 		    "alias", &galias);
847 		(void) nvlist_lookup_string(cfg->config_global_properties,
848 		    "auth", &gauth);
849 	}
850 
851 	for (; ptr != NULL; ptr = ptr->tgt_next) {
852 		if (found) {
853 			break;
854 		}
855 
856 		if (tgt) {
857 			/*
858 			 * We do a case-insensitive match in case
859 			 * a non-lower case value got stored.
860 			 */
861 			if (strcasecmp(tgt, ptr->tgt_name) != 0) {
862 				continue;
863 			} else {
864 				found = B_TRUE;
865 			}
866 		}
867 
868 		state = "-";
869 		num_sessions = 0;
870 		sess = NULL;
871 
872 		/*
873 		 * make a best effort to retrieve target status and
874 		 * number of active sessions from STMF.
875 		 */
876 		ret = stmfDevidFromIscsiName(ptr->tgt_name, &devid);
877 		if (ret == STMF_STATUS_SUCCESS) {
878 			ret = stmfGetTargetProperties(&devid, &props);
879 			if (ret == STMF_STATUS_SUCCESS) {
880 				if (props.status == STMF_TARGET_PORT_ONLINE) {
881 					state = "online";
882 				} else {
883 					state = "offline";
884 				}
885 			}
886 		}
887 		if (ret == STMF_STATUS_SUCCESS) {
888 			ret = stmfGetSessionList(&devid, &sess);
889 			if (ret == STMF_STATUS_SUCCESS) {
890 				num_sessions = sess->cnt;
891 				free(sess);
892 			}
893 		}
894 
895 		/* reset ret so we don't return an error */
896 		ret = 0;
897 
898 		if (!script && first) {
899 			(void) printf("%-61s%-9s%-9s\n", "TARGET NAME",
900 			    "STATE", "SESSIONS");
901 			first = B_FALSE;
902 		}
903 
904 		if (!script) {
905 			/*
906 			 * try not to let columns run into each other.
907 			 * Stick a tab after too-long fields.
908 			 * Lengths chosen are for the 'common' cases.
909 			 */
910 			(void) printf("%-61s", ptr->tgt_name);
911 			if (strlen(ptr->tgt_name) > 60) {
912 				(void) printf("\t");
913 			}
914 			(void) printf("%-9s%-9d", state, num_sessions);
915 		} else {
916 			(void) printf("%s\t%s\t%d", ptr->tgt_name,
917 			    state, num_sessions);
918 		}
919 
920 		if (!verbose) {
921 			(void) printf("\n");
922 			continue;
923 		}
924 
925 		auth = gauth;
926 		alias = galias;
927 		chapu = "-";
928 		chaps = "unset";
929 
930 		if (ptr->tgt_properties) {
931 			(void) nvlist_lookup_string(ptr->tgt_properties,
932 			    "auth", &auth);
933 			(void) nvlist_lookup_string(ptr->tgt_properties,
934 			    "alias", &alias);
935 			if (nvlist_exists(ptr->tgt_properties,
936 			    "targetchapsecret")) {
937 				chaps = "set";
938 			}
939 			(void) nvlist_lookup_string(ptr->tgt_properties,
940 			    "targetchapuser", &chapu);
941 		}
942 
943 		if (!script) {
944 			(void) printf("\n\t%-20s\t%s\n\t%-20s\t%s %s\n"
945 			    "\t%-20s\t%s\n\t%-20s\t%s\n\t%-20s\t",
946 			    "alias:", alias, "auth:", auth,
947 			    ((auth == gauth) ? "(defaults)" : ""),
948 			    "targetchapuser:",
949 			    chapu, "targetchapsecret:", chaps, "tpg-tags:");
950 		} else {
951 			(void) printf("\t%s\t%s %s\t%s\t%s\t",
952 			    alias, auth,
953 			    ((auth == gauth) ? "(defaults)" : ""),
954 			    chapu, chaps);
955 		}
956 
957 		first_tag = B_TRUE;
958 		tagp = ptr->tgt_tpgt_list;
959 		for (; tagp != NULL; tagp = tagp->tpgt_next) {
960 			if (!first_tag) {
961 				(void) printf(",");
962 			} else {
963 				first_tag = B_FALSE;
964 			}
965 			(void) printf("%s = %d",
966 			    tagp->tpgt_tpg_name, tagp->tpgt_tag);
967 		}
968 
969 		if (first_tag) {
970 			/* didn't find any */
971 			(void) printf("default");
972 		}
973 
974 		(void) printf("\n");
975 	}
976 
977 	if (tgt && (!found)) {
978 		(void) fprintf(stderr,
979 		    gettext("Target %s not found!"), tgt);
980 		(void) fprintf(stderr, "\n");
981 		ret = 1;
982 	}
983 
984 	it_config_free(cfg);
985 
986 	return (ret);
987 }
988 
989 int
delete_target(char * tgt,boolean_t force)990 delete_target(char *tgt, boolean_t force)
991 {
992 	int		ret;
993 	it_config_t	*cfg;
994 	it_tgt_t	*ptr;
995 	char		*sec = "solaris.smf.modify.stmf";
996 
997 	ITADM_CHKAUTH(sec);
998 
999 	if (!tgt) {
1000 		(void) fprintf(stderr, "%s\n",
1001 		    gettext("Error, no target specified"));
1002 		return (EINVAL);
1003 	}
1004 
1005 	ret = it_config_load(&cfg);
1006 	if (ret != 0) {
1007 		output_config_error(ret,
1008 		    gettext("Error retrieving iSCSI target configuration"));
1009 		return (ret);
1010 	}
1011 
1012 	ptr = cfg->config_tgt_list;
1013 	while (ptr) {
1014 		/*
1015 		 * We do a case-insensitive match in case
1016 		 * a non-lower case value got stored.
1017 		 */
1018 		if (strcasecmp(ptr->tgt_name, tgt) == 0) {
1019 			break;
1020 		}
1021 
1022 		ptr = ptr->tgt_next;
1023 	}
1024 
1025 	if (ptr) {
1026 		ret = it_tgt_delete(cfg, ptr, force);
1027 
1028 		if (ret != 0) {
1029 			if (ret == EBUSY) {
1030 				(void) fprintf(stderr,
1031 				    gettext("The target is online or busy. "
1032 				    "Use the -f (force) option, or "
1033 				    "'stmfadm offline-target %s'"), tgt);
1034 				(void) fprintf(stderr, "\n");
1035 			} else {
1036 				output_config_error(ret, gettext(
1037 				    "Error deleting target"));
1038 			}
1039 		}
1040 
1041 		if (ret == 0) {
1042 			ret = it_config_commit(cfg);
1043 			STMF_STALE(ret);
1044 		}
1045 	} else {
1046 		(void) fprintf(stderr,
1047 		    gettext("Target %s not found"), tgt);
1048 		(void) fprintf(stderr, "\n");
1049 		ret = 1;
1050 	}
1051 
1052 	it_config_free(cfg);
1053 
1054 	return (ret);
1055 }
1056 
1057 static int
modify_target(char * tgt,char * newname,nvlist_t * proplist)1058 modify_target(char *tgt, char *newname, nvlist_t *proplist)
1059 {
1060 	int		ret;
1061 	it_config_t	*cfg = NULL;
1062 	it_tgt_t	*ptr = NULL;
1063 	it_tgt_t	*tgtp = NULL;
1064 	char		**tags = NULL;
1065 	uint32_t	count = 0;
1066 	nvlist_t	*errlist = NULL;
1067 	int		i;
1068 	it_tpg_t	*tpg = NULL;
1069 	uint16_t	tagid;
1070 	it_tpgt_t	*tpgt = NULL;
1071 	char		*sec = "solaris.smf.modify.stmf";
1072 	boolean_t	did_it_config_load = B_FALSE;
1073 
1074 	ITADM_CHKAUTH(sec);
1075 
1076 	/* XXX:  Do we need to offline anything here too? */
1077 
1078 	if (!tgt) {
1079 		(void) fprintf(stderr, "%s\n",
1080 		    gettext("Error, no target specified"));
1081 		ret = EINVAL;
1082 		goto done;
1083 	}
1084 
1085 	ret = it_config_load(&cfg);
1086 	if (ret != 0) {
1087 		output_config_error(ret,
1088 		    gettext("Error retrieving iSCSI target configuration"));
1089 		goto done;
1090 	}
1091 
1092 	did_it_config_load = B_TRUE;
1093 
1094 	/*
1095 	 * If newname is specified, ensure it is a valid name.
1096 	 */
1097 	if (newname) {
1098 		if (!validate_iscsi_name(newname)) {
1099 			(void) fprintf(stderr,
1100 			    gettext("Invalid iSCSI name %s"), newname);
1101 			(void) fprintf(stderr, "\n");
1102 			ret = 1;
1103 			goto done;
1104 		}
1105 	}
1106 
1107 	/*
1108 	 * Loop through to verify that the target to be modified truly
1109 	 * exists.  If this target is to be renamed, ensure the new
1110 	 * name is not already in use.
1111 	 */
1112 	ptr = cfg->config_tgt_list;
1113 	while (ptr) {
1114 		/*
1115 		 * Does a target with the new name already exist?
1116 		 */
1117 		if (newname &&
1118 		    (strcasecmp(newname, ptr->tgt_name) == 0)) {
1119 			(void) fprintf(stderr,
1120 			    gettext("A target with name %s already exists"),
1121 			    newname);
1122 			(void) fprintf(stderr, "\n");
1123 			ret = 1;
1124 			goto done;
1125 		}
1126 
1127 		if (strcasecmp(ptr->tgt_name, tgt) == 0) {
1128 			tgtp = ptr;
1129 		}
1130 
1131 		ptr = ptr ->tgt_next;
1132 	}
1133 
1134 	if (!tgtp) {
1135 		(void) fprintf(stderr,
1136 		    gettext("Target %s not found"), tgt);
1137 		(void) fprintf(stderr, "\n");
1138 		ret = EINVAL;
1139 		goto done;
1140 	}
1141 
1142 	/* set the target portal group tags */
1143 	ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
1144 	    &count);
1145 
1146 	if (ret == ENOENT) {
1147 		/* none specified.  is this ok? */
1148 		ret = 0;
1149 	} else if (ret != 0) {
1150 		output_config_error(ret, gettext("Internal error"));
1151 		goto done;
1152 	}
1153 
1154 	/* special case, remove all explicit TPGs, and don't add any */
1155 	if (tags && (count == 1) && (strcmp("default", tags[0]) == 0)) {
1156 		count = 0;
1157 	}
1158 
1159 	for (i = 0; i < count; i++) {
1160 		if (!tags || !tags[i]) {
1161 			continue;
1162 		}
1163 
1164 		/* see that all referenced groups are already defined */
1165 		tpg = cfg->config_tpg_list;
1166 		while (tpg != NULL) {
1167 			if (strcmp(tags[i], tpg->tpg_name) == 0) {
1168 				break;
1169 			}
1170 			tpg = tpg->tpg_next;
1171 		}
1172 		if (tpg == NULL) {
1173 			(void) fprintf(stderr,
1174 			    gettext("Invalid tpg-name %s: not defined"),
1175 			    tags[i]);
1176 			(void) fprintf(stderr, "\n");
1177 			ret = 1;
1178 			goto done;
1179 		}
1180 	}
1181 
1182 	/*
1183 	 * don't recreate tags that are already associated,
1184 	 * remove tags not requested.
1185 	 */
1186 	if (tags) {
1187 		tpgt = tgtp->tgt_tpgt_list;
1188 		while (tpgt) {
1189 			for (i = 0; i < count; i++) {
1190 				if (!tags[i]) {
1191 					continue;
1192 				}
1193 
1194 				if (strcmp(tpgt->tpgt_tpg_name, tags[i])
1195 				    == 0) {
1196 					/* non-null tags will be created */
1197 					tags[i] = NULL;
1198 					break;
1199 				}
1200 			}
1201 			if (i == count) {
1202 				/* one to remove */
1203 				it_tpgt_t	*ptr = tpgt;
1204 
1205 				tpgt = ptr->tpgt_next;
1206 				it_tpgt_delete(cfg, tgtp, ptr);
1207 			} else {
1208 				tpgt = tpgt->tpgt_next;
1209 			}
1210 		}
1211 	}
1212 
1213 	/* see if there are any left to add */
1214 	for (i = 0; i < count; i++) {
1215 		if (!tags || !tags[i]) {
1216 			continue;
1217 		}
1218 
1219 		/* generate the tag number to use */
1220 		tag_name_to_num(tags[i], &tagid);
1221 
1222 		ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
1223 		if (ret != 0) {
1224 			if (ret == E2BIG) {
1225 				(void) fprintf(stderr, "%s\n",
1226 				    gettext("Error, no portal tag available"));
1227 			} else {
1228 				(void) fprintf(stderr, gettext(
1229 				    "Could not add target portal group"
1230 				    " tag %s: "), tags[i]);
1231 				output_config_error(ret, NULL);
1232 			}
1233 			goto done;
1234 		}
1235 	}
1236 
1237 	/* remove the tags from the proplist before continuing */
1238 	(void) nvlist_remove_all(proplist, "tpg-tag");
1239 
1240 	/*
1241 	 * Rename this target, if requested.  Save the old name in
1242 	 * the property list, so the kernel knows this is a renamed
1243 	 * target, and not a new one.
1244 	 */
1245 	if (newname && (strlen(newname) > 0)) {
1246 		ret = nvlist_add_string(proplist, "oldtargetname",
1247 		    tgtp->tgt_name);
1248 		if (ret != 0) {
1249 			output_config_error(ret,
1250 			    gettext("Error renaming target"));
1251 			goto done;
1252 		}
1253 		(void) strlcpy(tgtp->tgt_name, newname,
1254 		    sizeof (tgtp->tgt_name));
1255 	}
1256 
1257 	ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
1258 	if (ret != 0) {
1259 		(void) fprintf(stderr,
1260 		    gettext("Error setting target properties: %d"), ret);
1261 		(void) fprintf(stderr, "\n");
1262 		if (errlist) {
1263 			nvpair_t	*nvp = NULL;
1264 			char		*nn;
1265 			char		*nv;
1266 
1267 			while ((nvp = nvlist_next_nvpair(errlist, nvp))
1268 			    != NULL) {
1269 				nv = NULL;
1270 
1271 				nn = nvpair_name(nvp);
1272 				(void) nvpair_value_string(nvp, &nv);
1273 
1274 				if (nv != NULL) {
1275 					(void) fprintf(stderr, "\t%s: %s\n",
1276 					    nn, nv);
1277 				}
1278 			}
1279 
1280 			nvlist_free(errlist);
1281 		}
1282 		goto done;
1283 	}
1284 
1285 	if (ret == 0) {
1286 		ret = it_config_commit(cfg);
1287 		STMF_STALE(ret);
1288 	}
1289 
1290 done:
1291 	if (ret == 0) {
1292 		(void) printf(gettext("Target %s successfully modified"),
1293 		    tgtp->tgt_name);
1294 		(void) printf("\n");
1295 	}
1296 
1297 	if (did_it_config_load)
1298 		it_config_free(cfg);
1299 
1300 	return (ret);
1301 }
1302 
1303 int
create_tpg(char * tpg,int addrc,char ** addrs)1304 create_tpg(char *tpg, int addrc, char **addrs)
1305 {
1306 	int		ret;
1307 	it_config_t	*cfg;
1308 	it_tpg_t	*tpgp;
1309 	int		count = 0;
1310 	it_portal_t	*ptl;
1311 	char		*sec = "solaris.smf.modify.stmf";
1312 	int 		i = 0;
1313 
1314 	ITADM_CHKAUTH(sec);
1315 
1316 	if (!tpg) {
1317 		(void) fprintf(stderr, "%s\n",
1318 		    gettext("Error, no target portal group specified"));
1319 		return (EINVAL);
1320 	}
1321 
1322 	if (strlen(tpg) > (MAX_TPG_NAMELEN - 1)) {
1323 		(void) fprintf(stderr,
1324 		    gettext("Target Portal Group name must be no longer "
1325 		    "than %d characters"), (MAX_TPG_NAMELEN - 1));
1326 		(void) fprintf(stderr, "\n");
1327 		return (EINVAL);
1328 	}
1329 
1330 	if (!addrs || (addrc <= 0)) {
1331 		(void) fprintf(stderr, "%s\n",
1332 		    gettext("Error, no portal addresses specified"));
1333 		return (EINVAL);
1334 	}
1335 
1336 	ret = it_config_load(&cfg);
1337 	if (ret != 0) {
1338 		output_config_error(ret,
1339 		    gettext("Error retrieving iSCSI target configuration"));
1340 		return (ret);
1341 	}
1342 
1343 	tpgp = cfg->config_tpg_list;
1344 	while (tpgp != NULL) {
1345 		if (strcmp(tpgp->tpg_name, tpg) == 0) {
1346 			(void) fprintf(stderr,
1347 			    gettext("Target Portal Group %s already exists"),
1348 			    tpg);
1349 			(void) fprintf(stderr, "\n");
1350 			it_config_free(cfg);
1351 			return (1);
1352 		}
1353 		tpgp = tpgp->tpg_next;
1354 	}
1355 
1356 	/*
1357 	 * Ensure that the addrs don't contain commas.
1358 	 */
1359 	for (i = 0; i < addrc; i++) {
1360 		if (strchr(addrs[i], ',')) {
1361 			(void) fprintf(stderr,
1362 			    gettext("Bad portal name %s"),
1363 			    addrs[i]);
1364 			(void) fprintf(stderr, "\n");
1365 
1366 			it_config_free(cfg);
1367 			return (EINVAL);
1368 		}
1369 	}
1370 
1371 	/*
1372 	 * Create the portal group and first portal
1373 	 */
1374 	ret = it_tpg_create(cfg, &tpgp, tpg, addrs[count]);
1375 	if (ret != 0) {
1376 		if (ret == EEXIST) {
1377 			(void) fprintf(stderr,
1378 			    gettext("Portal %s already in use"),
1379 			    addrs[count]);
1380 			(void) fprintf(stderr, "\n");
1381 		} else {
1382 			output_config_error(ret, gettext("Could not create the "
1383 			    "target portal group"));
1384 		}
1385 		it_config_free(cfg);
1386 		return (ret);
1387 	}
1388 
1389 	/*
1390 	 * Add the remaining portals
1391 	 */
1392 	for (count = 1; count < addrc; count++) {
1393 		if (!addrs[count]) {
1394 			continue;
1395 		}
1396 
1397 		ret = it_portal_create(cfg, tpgp, &ptl, addrs[count]);
1398 		if (ret != 0) {
1399 			if (ret == EEXIST) {
1400 				(void) fprintf(stderr,
1401 				    gettext("Portal %s already in use"),
1402 				    addrs[count]);
1403 				(void) fprintf(stderr, "\n");
1404 			} else {
1405 				(void) fprintf(stderr,
1406 				    gettext("Error adding portal %s: "),
1407 				    addrs[count]);
1408 				output_config_error(ret, NULL);
1409 				break;
1410 			}
1411 		}
1412 	}
1413 
1414 	if (ret == 0) {
1415 		ret = it_config_commit(cfg);
1416 		STMF_STALE(ret);
1417 	}
1418 
1419 	it_config_free(cfg);
1420 
1421 	return (ret);
1422 }
1423 
1424 static int
list_tpg(char * tpg,boolean_t verbose,boolean_t script)1425 list_tpg(char *tpg, boolean_t verbose, boolean_t script)
1426 {
1427 	int		ret;
1428 	it_config_t	*cfg;
1429 	it_tpg_t	*ptr;
1430 	boolean_t	found = B_FALSE;
1431 	it_portal_t	*portal;
1432 	boolean_t	first = B_TRUE;
1433 	boolean_t	first_portal;
1434 	char		*pstr;
1435 	char		*sec = "solaris.smf.read.stmf";
1436 
1437 	ITADM_CHKAUTH(sec);
1438 
1439 	ret = it_config_load(&cfg);
1440 	if (ret != 0) {
1441 		output_config_error(ret,
1442 		    gettext("Error retrieving iSCSI target configuration"));
1443 		return (ret);
1444 	}
1445 
1446 	ptr = cfg->config_tpg_list;
1447 
1448 	for (; ptr != NULL; ptr = ptr->tpg_next) {
1449 		if (found) {
1450 			break;
1451 		}
1452 
1453 		if (tpg) {
1454 			if (strcmp(tpg, ptr->tpg_name) != 0) {
1455 				continue;
1456 			} else {
1457 				found = B_TRUE;
1458 			}
1459 		}
1460 
1461 		if (!script && first) {
1462 			(void) printf("%-30s%-9s\n", "TARGET PORTAL GROUP",
1463 			    "PORTAL COUNT");
1464 			first = B_FALSE;
1465 		}
1466 
1467 		if (!script) {
1468 			(void) printf("%-30s", ptr->tpg_name);
1469 			if (strlen(ptr->tpg_name) > 30) {
1470 				(void) printf("\t");
1471 			}
1472 			(void) printf("%-9d", ptr->tpg_portal_count);
1473 		} else {
1474 			(void) printf("%s\t%d", ptr->tpg_name,
1475 			    ptr->tpg_portal_count);
1476 		}
1477 
1478 		if (!verbose) {
1479 			(void) printf("\n");
1480 			continue;
1481 		}
1482 
1483 		if (!script) {
1484 			(void) printf("\n    portals:");
1485 		}
1486 
1487 		first_portal = B_TRUE;
1488 
1489 		portal = ptr->tpg_portal_list;
1490 		for (; portal != NULL; portal = portal->portal_next) {
1491 			ret = sockaddr_to_str(&(portal->portal_addr), &pstr);
1492 			if (ret != 0) {
1493 				/* invalid addr? */
1494 				continue;
1495 			}
1496 			if (!first_portal) {
1497 				(void) printf(",");
1498 			} else {
1499 				(void) printf("\t");
1500 				first_portal = B_FALSE;
1501 			}
1502 
1503 			(void) printf("%s", pstr);
1504 			free(pstr);
1505 		}
1506 
1507 		if (first_portal) {
1508 			/* none found */
1509 			(void) printf("\t<none>");
1510 		}
1511 
1512 		(void) printf("\n");
1513 	}
1514 
1515 	if (tpg && (!found)) {
1516 		(void) fprintf(stderr,
1517 		    gettext("Target Portal Group %s not found!\n"), tpg);
1518 		(void) fprintf(stderr, "\n");
1519 		ret = 1;
1520 	}
1521 
1522 	it_config_free(cfg);
1523 
1524 	return (ret);
1525 }
1526 
1527 static int
delete_tpg(char * tpg,boolean_t force)1528 delete_tpg(char *tpg, boolean_t force)
1529 {
1530 	int		ret;
1531 	it_config_t	*cfg;
1532 	it_tpg_t	*ptpg = NULL;
1533 	char		*sec = "solaris.smf.modify.stmf";
1534 
1535 	ITADM_CHKAUTH(sec);
1536 
1537 	if (!tpg) {
1538 		(void) fprintf(stderr, "%s\n",
1539 		    gettext("Error, no target portal group specified"));
1540 		return (EINVAL);
1541 	}
1542 
1543 	ret = it_config_load(&cfg);
1544 	if (ret != 0) {
1545 		output_config_error(ret,
1546 		    gettext("Error retrieving iSCSI target configuration"));
1547 		return (ret);
1548 	}
1549 
1550 	ptpg = cfg->config_tpg_list;
1551 	for (; ptpg != NULL; ptpg = ptpg->tpg_next) {
1552 		if (strcmp(tpg, ptpg->tpg_name) == 0) {
1553 			break;
1554 		}
1555 	}
1556 
1557 	if (!ptpg) {
1558 		(void) fprintf(stderr,
1559 		    gettext("Target portal group %s does not exist"),
1560 		    tpg);
1561 		(void) fprintf(stderr, "\n");
1562 		ret = 1;
1563 	} else {
1564 		ret = it_tpg_delete(cfg, ptpg, force);
1565 		if (ret == EBUSY) {
1566 			(void) fprintf(stderr, "%s\n",
1567 			    gettext(
1568 			    "Target portal group associated with one or more "
1569 			    "targets.  Cannot delete."));
1570 		} else if (ret != 0) {
1571 			output_config_error(ret, gettext("Could not delete "
1572 			    "target portal group"));
1573 		}
1574 
1575 		if (ret == 0) {
1576 			ret = it_config_commit(cfg);
1577 			STMF_STALE(ret);
1578 		}
1579 	}
1580 
1581 	it_config_free(cfg);
1582 
1583 	return (ret);
1584 }
1585 
1586 static int
modify_initiator(char * ini,nvlist_t * proplist,boolean_t create)1587 modify_initiator(char *ini, nvlist_t *proplist, boolean_t create)
1588 {
1589 	int		ret;
1590 	it_config_t	*cfg;
1591 	it_ini_t	*inip;
1592 	nvlist_t	*errlist = NULL;
1593 	nvpair_t	*nvp = NULL;
1594 	char		*sec = "solaris.smf.modify.stmf";
1595 	boolean_t	changed = B_TRUE;
1596 
1597 	ITADM_CHKAUTH(sec);
1598 
1599 	if (!ini) {
1600 		(void) fprintf(stderr, "%s\n",
1601 		    gettext("Error, no initiator specified"));
1602 		return (EINVAL);
1603 	} else if (create) {
1604 		/*
1605 		 * validate input name - what are the rules for EUI
1606 		 * and IQN values?
1607 		 */
1608 		if (!IS_IQN_NAME(ini) && !IS_EUI_NAME(ini)) {
1609 			(void) fprintf(stderr, gettext("Invalid name %s"),
1610 			    ini);
1611 			(void) fprintf(stderr, "\n");
1612 			return (EINVAL);
1613 		}
1614 	}
1615 
1616 	/*
1617 	 * See if any properties were actually specified.
1618 	 */
1619 	if (proplist) {
1620 		nvp = nvlist_next_nvpair(proplist, nvp);
1621 	}
1622 
1623 	if ((nvp == NULL) && !create) {
1624 		changed = B_FALSE;
1625 	}
1626 
1627 	/*
1628 	 * If no properties, and this is really a modify op, verify
1629 	 * that the requested initiator exists, but then don't do anything.
1630 	 * Modifying non-existent is an error; doing nothing to a defined
1631 	 * initiator is not.
1632 	 */
1633 
1634 	ret = it_config_load(&cfg);
1635 	if (ret != 0) {
1636 		output_config_error(ret,
1637 		    gettext("Error retrieving iSCSI target configuration"));
1638 		return (ret);
1639 	}
1640 
1641 	inip = cfg->config_ini_list;
1642 	while (inip) {
1643 		if (strcasecmp(inip->ini_name, ini) == 0) {
1644 			break;
1645 		}
1646 
1647 		inip = inip->ini_next;
1648 	}
1649 
1650 	if (create) {
1651 		if (inip) {
1652 			(void) fprintf(stderr,
1653 			    gettext("Initiator %s already exists"),
1654 			    inip->ini_name);
1655 			(void) fprintf(stderr, "\n");
1656 			ret = EINVAL;
1657 		} else {
1658 			ret = it_ini_create(cfg, &inip, ini);
1659 			if (ret != 0) {
1660 				if (ret == EFAULT) {
1661 					(void) fprintf(stderr,
1662 					    gettext("Invalid iSCSI name %s"),
1663 					    ini);
1664 					(void) fprintf(stderr, "\n");
1665 				} else {
1666 					output_config_error(ret, gettext(
1667 					    "Error creating initiator"));
1668 				}
1669 			}
1670 		}
1671 	} else if (!inip) {
1672 		ret = ENOENT;
1673 		(void) fprintf(stderr,
1674 		    gettext("Error, initiator %s not found"),
1675 		    ini);
1676 		(void) fprintf(stderr, "\n");
1677 	}
1678 
1679 	if ((ret == 0) && nvp) {
1680 		ret = it_ini_setprop(inip, proplist, &errlist);
1681 
1682 		if (ret != 0) {
1683 			(void) fprintf(stderr,
1684 			    gettext("Error setting initiator properties: %d"),
1685 			    ret);
1686 			(void) fprintf(stderr, "\n");
1687 			if (errlist) {
1688 				nvpair_t	*nvp = NULL;
1689 				char		*nn;
1690 				char		*nv;
1691 
1692 				while ((nvp = nvlist_next_nvpair(errlist, nvp))
1693 				    != NULL) {
1694 					nv = NULL;
1695 
1696 					nn = nvpair_name(nvp);
1697 					(void) nvpair_value_string(nvp, &nv);
1698 
1699 					if (nv != NULL) {
1700 						(void) fprintf(stderr,
1701 						    "\t%s: %s\n", nn, nv);
1702 					}
1703 				}
1704 
1705 				nvlist_free(errlist);
1706 			}
1707 		}
1708 	}
1709 
1710 	if ((ret == 0) && changed) {
1711 		ret = it_config_commit(cfg);
1712 		STMF_STALE(ret);
1713 	}
1714 
1715 	it_config_free(cfg);
1716 
1717 	return (ret);
1718 }
1719 
1720 static int
list_initiator(char * ini,boolean_t verbose,boolean_t script)1721 list_initiator(char *ini, boolean_t verbose, boolean_t script) /* ARGSUSED */
1722 {
1723 	int		ret;
1724 	it_config_t	*cfg;
1725 	it_ini_t	*ptr;
1726 	boolean_t	found = B_FALSE;
1727 	boolean_t	first = B_TRUE;
1728 	char		*isecret;
1729 	char		*iuser;
1730 	char		*sec = "solaris.smf.read.stmf";
1731 
1732 	ITADM_CHKAUTH(sec);
1733 
1734 	ret = it_config_load(&cfg);
1735 	if (ret != 0) {
1736 		output_config_error(ret,
1737 		    gettext("Error retrieving iSCSI target configuration"));
1738 		return (ret);
1739 	}
1740 
1741 	ptr = cfg->config_ini_list;
1742 
1743 	for (; ptr != NULL; ptr = ptr->ini_next) {
1744 		isecret = "unset";
1745 		iuser = "<none>";
1746 
1747 		if (found) {
1748 			break;
1749 		}
1750 
1751 		if (ini) {
1752 			if (strcasecmp(ini, ptr->ini_name) != 0) {
1753 				continue;
1754 			} else {
1755 				found = B_TRUE;
1756 			}
1757 		}
1758 
1759 		if (ptr->ini_properties) {
1760 			if (nvlist_exists(ptr->ini_properties, "chapsecret")) {
1761 				isecret = "set";
1762 			}
1763 			(void) nvlist_lookup_string(ptr->ini_properties,
1764 			    "chapuser", &iuser);
1765 
1766 		}
1767 
1768 		/* there's nothing to print for verbose yet */
1769 		if (!script && first) {
1770 			(void) printf("%-61s%-10s%-7s\n", "INITIATOR NAME",
1771 			    "CHAPUSER", "SECRET");
1772 			first = B_FALSE;
1773 		}
1774 
1775 		if (!script) {
1776 			/*
1777 			 * try not to let columns run into each other.
1778 			 * Stick a tab after too-long fields.
1779 			 * Lengths chosen are for the 'common' cases.
1780 			 */
1781 			(void) printf("%-61s", ptr->ini_name);
1782 
1783 			if (strlen(ptr->ini_name) > 60) {
1784 				(void) printf("\t");
1785 			}
1786 
1787 			(void) printf("%-15s", iuser);
1788 			if (strlen(iuser) >= 15) {
1789 				(void) printf("\t");
1790 			}
1791 
1792 			(void) printf("%-4s", isecret);
1793 		} else {
1794 			(void) printf("%s\t%s\t%s", ptr->ini_name,
1795 			    iuser, isecret);
1796 		}
1797 
1798 		(void) printf("\n");
1799 	}
1800 
1801 	if (ini && (!found)) {
1802 		(void) fprintf(stderr,
1803 		    gettext("Initiator %s not found!"), ini);
1804 		(void) fprintf(stderr, "\n");
1805 		ret = 1;
1806 	}
1807 
1808 	it_config_free(cfg);
1809 
1810 	return (ret);
1811 }
1812 
1813 int
delete_initiator(char * ini)1814 delete_initiator(char *ini)
1815 {
1816 	int		ret;
1817 	it_config_t	*cfg;
1818 	it_ini_t	*ptr;
1819 	char		*sec = "solaris.smf.modify.stmf";
1820 
1821 	ITADM_CHKAUTH(sec);
1822 
1823 	if (!ini) {
1824 		(void) fprintf(stderr, "%s\n",
1825 		    gettext("Error, no initiator specified"));
1826 		return (EINVAL);
1827 	}
1828 
1829 	ret = it_config_load(&cfg);
1830 	if (ret != 0) {
1831 		output_config_error(ret,
1832 		    gettext("Error retrieving iSCSI target configuration"));
1833 		return (ret);
1834 	}
1835 
1836 	ptr = cfg->config_ini_list;
1837 	while (ptr) {
1838 		if (strcasecmp(ptr->ini_name, ini) == 0) {
1839 			break;
1840 		}
1841 
1842 		ptr = ptr->ini_next;
1843 	}
1844 
1845 	if (ptr) {
1846 		it_ini_delete(cfg, ptr);
1847 
1848 		ret = it_config_commit(cfg);
1849 		STMF_STALE(ret);
1850 	} else {
1851 		(void) fprintf(stderr,
1852 		    gettext("Initiator %s not found"), ini);
1853 		(void) fprintf(stderr, "\n");
1854 		ret = 1;
1855 	}
1856 
1857 	return (ret);
1858 }
1859 
1860 static int
modify_defaults(nvlist_t * proplist)1861 modify_defaults(nvlist_t *proplist)
1862 {
1863 	int		ret;
1864 	it_config_t	*cfg;
1865 	nvlist_t	*errlist = NULL;
1866 	nvpair_t	*nvp = NULL;
1867 	char		*sec = "solaris.smf.modify.stmf";
1868 
1869 	ITADM_CHKAUTH(sec);
1870 
1871 	if (proplist) {
1872 		/* make sure at least one property is specified */
1873 		nvp = nvlist_next_nvpair(proplist, nvp);
1874 	}
1875 
1876 	if (nvp == NULL) {
1877 		/* empty list */
1878 		(void) fprintf(stderr, "%s\n",
1879 		    gettext("Error, no properties specified"));
1880 		return (EINVAL);
1881 	}
1882 
1883 	ret = it_config_load(&cfg);
1884 	if (ret != 0) {
1885 		output_config_error(ret,
1886 		    gettext("Error retrieving iSCSI target configuration"));
1887 		return (ret);
1888 	}
1889 
1890 	ret = it_config_setprop(cfg, proplist, &errlist);
1891 	if (ret != 0) {
1892 		(void) fprintf(stderr,
1893 		    gettext("Error setting global properties: %d"),
1894 		    ret);
1895 		(void) fprintf(stderr, "\n");
1896 		if (errlist) {
1897 			nvpair_t	*nvp = NULL;
1898 			char		*nn;
1899 			char		*nv;
1900 
1901 			while ((nvp = nvlist_next_nvpair(errlist, nvp))
1902 			    != NULL) {
1903 				nv = NULL;
1904 
1905 				nn = nvpair_name(nvp);
1906 				(void) nvpair_value_string(nvp, &nv);
1907 
1908 				if (nv != NULL) {
1909 					(void) fprintf(stderr, "\t%s: %s\n",
1910 					    nn, nv);
1911 				}
1912 			}
1913 
1914 			nvlist_free(errlist);
1915 		}
1916 	}
1917 
1918 	if (ret == 0) {
1919 		ret = it_config_commit(cfg);
1920 		STMF_STALE(ret);
1921 	}
1922 
1923 	it_config_free(cfg);
1924 
1925 	return (ret);
1926 }
1927 
1928 static int
list_defaults(boolean_t script)1929 list_defaults(boolean_t script)
1930 {
1931 	int		ret;
1932 	it_config_t	*cfg;
1933 	nvlist_t	*nvl;
1934 	char		*alias = "<none>";
1935 	char		*auth = "<none>";
1936 	char		*isns = "disabled";
1937 	char		**isvrs = NULL;
1938 	uint32_t	scount = 0;
1939 	char		*rsvr = "<none>";
1940 	char		*rsecret = "unset";
1941 	boolean_t	val = B_FALSE;
1942 	int		i;
1943 	char		*sec = "solaris.smf.read.stmf";
1944 
1945 	ITADM_CHKAUTH(sec);
1946 
1947 	ret = it_config_load(&cfg);
1948 	if (ret != 0) {
1949 		output_config_error(ret,
1950 		    gettext("Error retrieving iSCSI target configuration"));
1951 		return (ret);
1952 	}
1953 
1954 	nvl = cfg->config_global_properties;
1955 
1956 	/* look up all possible options */
1957 	(void) nvlist_lookup_string(nvl, "alias", &alias);
1958 	(void) nvlist_lookup_string(nvl, "auth", &auth);
1959 	(void) nvlist_lookup_boolean_value(nvl, "isns", &val);
1960 	if (val == B_TRUE) {
1961 		isns = "enabled";
1962 	}
1963 	(void) nvlist_lookup_string_array(nvl, "isnsserver", &isvrs,
1964 	    &scount);
1965 	(void) nvlist_lookup_string(nvl, "radiusserver", &rsvr);
1966 	if (nvlist_exists(nvl, "radiussecret")) {
1967 		rsecret = "set";
1968 	}
1969 
1970 	if (!script) {
1971 		(void) printf("%s:\n\n",
1972 		    gettext("iSCSI Target Default Properties"));
1973 	}
1974 
1975 	if (script) {
1976 		(void) printf("%s\t%s\t%s\t%s\t%s\t",
1977 		    alias, auth, rsvr, rsecret, isns);
1978 	} else {
1979 		(void) printf("%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n"
1980 		    "%-15s\t%s\n%-15s\t",
1981 		    "alias:", alias, "auth:", auth, "radiusserver:",
1982 		    rsvr, "radiussecret:", rsecret, "isns:", isns,
1983 		    "isnsserver:");
1984 	}
1985 
1986 	for (i = 0; i < scount; i++) {
1987 		if (!isvrs || !isvrs[i]) {
1988 			break;
1989 		}
1990 		if (i > 0) {
1991 			(void) printf(",");
1992 		}
1993 		(void) printf("%s", isvrs[i]);
1994 	}
1995 
1996 	if (i == 0) {
1997 		(void) printf("%s", "<none>");
1998 	}
1999 
2000 	(void) printf("\n");
2001 
2002 	it_config_free(cfg);
2003 
2004 	return (0);
2005 }
2006 
2007 static int
itadm_get_password(nvlist_t * nvl,char * key,char * passfile,char * phrase)2008 itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
2009     char *phrase)
2010 {
2011 	int		ret = 0;
2012 	char		*pass;
2013 	char		buf[1024];
2014 	int		fd;
2015 	struct stat64	sbuf;
2016 	size_t		rd;
2017 
2018 	if (!nvl || !key) {
2019 		return (EINVAL);
2020 	}
2021 
2022 	if (passfile) {
2023 		ret = stat64(passfile, &sbuf);
2024 		if ((ret != 0) || (!S_ISREG(sbuf.st_mode))) {
2025 			(void) fprintf(stderr,
2026 			    gettext("Invalid secret file %s"),
2027 			    passfile);
2028 			(void) fprintf(stderr, "\n");
2029 			return (EBADF);
2030 		}
2031 
2032 		fd = open64(passfile, O_RDONLY);
2033 		if (fd == -1) {
2034 			ret = errno;
2035 			(void) fprintf(stderr,
2036 			    gettext("Could not open secret file %s: "),
2037 			    passfile);
2038 			output_config_error(ret, NULL);
2039 			return (ret);
2040 		}
2041 
2042 		rd = read(fd, buf, sbuf.st_size);
2043 		(void) close(fd);
2044 
2045 		if (rd != sbuf.st_size) {
2046 			ret = EIO;
2047 			(void) fprintf(stderr,
2048 			    gettext("Could not read secret file %s: "),
2049 			    passfile);
2050 			output_config_error(ret, NULL);
2051 			return (ret);
2052 		}
2053 
2054 		/* ensure buf is properly terminated */
2055 		buf[rd] = '\0';
2056 
2057 		/* if last char is a newline, strip it off */
2058 		if (buf[rd - 1] == '\n') {
2059 			buf[rd - 1] = '\0';
2060 		}
2061 
2062 		/* validate length */
2063 		if ((strlen(buf) > 255) || (strlen(buf) < 12)) {
2064 			(void) fprintf(stderr, "%s\n",
2065 			    gettext(
2066 			    "Secret must be between 12 and 255 characters"));
2067 			return (EINVAL);
2068 		}
2069 	} else {
2070 		/* prompt for secret */
2071 		if (!phrase) {
2072 			return (EINVAL);
2073 		}
2074 
2075 		pass = getpassphrase(phrase);
2076 		if (!pass) {
2077 			ret = errno;
2078 			output_config_error(ret,
2079 			    gettext("Could not read secret"));
2080 			return (ret);
2081 		}
2082 
2083 		/* validate length */
2084 		if ((strlen(pass) > 255) || (strlen(pass) < 12)) {
2085 			(void) fprintf(stderr, "%s\n",
2086 			    gettext(
2087 			    "Secret must be between 12 and 255 characters"));
2088 			return (EINVAL);
2089 		}
2090 
2091 		(void) strlcpy(buf, pass, sizeof (buf));
2092 
2093 		/* confirm entered secret */
2094 		pass = getpassphrase(gettext("Re-enter secret: "));
2095 		if (!pass) {
2096 			ret = errno;
2097 			output_config_error(ret,
2098 			    gettext("Could not read secret"));
2099 			return (ret);
2100 		}
2101 
2102 		if (strcmp(buf, pass) != 0) {
2103 			ret = EINVAL;
2104 			(void) fprintf(stderr, "%s\n",
2105 			    gettext("Secret validation failed"));
2106 			return (ret);
2107 		}
2108 
2109 	}
2110 
2111 	ret = nvlist_add_string(nvl, key, buf);
2112 
2113 	return (ret);
2114 }
2115 
2116 static int
itadm_opt_to_arr(nvlist_t * nvl,char * key,char * opt,uint32_t * num)2117 itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num)
2118 {
2119 	int		count;
2120 	char		*bufp;
2121 	char		**arr;
2122 
2123 	if (!opt || !key || !nvl) {
2124 		return (EINVAL);
2125 	}
2126 
2127 	bufp = opt;
2128 	count = 1;
2129 
2130 	for (;;) {
2131 		bufp = strchr(bufp, ',');
2132 		if (!bufp) {
2133 			break;
2134 		}
2135 		bufp++;
2136 		count++;
2137 	}
2138 
2139 	arr = calloc(count, sizeof (char *));
2140 	if (!arr) {
2141 		return (ENOMEM);
2142 	}
2143 
2144 	bufp = opt;
2145 	/* set delimiter to comma */
2146 	(void) bufsplit(",", 0, NULL);
2147 
2148 	/* split up that buf! */
2149 	(void) bufsplit(bufp, count, arr);
2150 
2151 	/* if requested, return the number of array members found */
2152 	if (num) {
2153 		*num = count;
2154 	}
2155 
2156 	return (nvlist_add_string_array(nvl, key, arr, count));
2157 }
2158 
2159 static void
tag_name_to_num(char * tagname,uint16_t * tagnum)2160 tag_name_to_num(char *tagname, uint16_t *tagnum)
2161 {
2162 	ulong_t		id;
2163 	char		*ptr = NULL;
2164 
2165 	if (!tagname || !tagnum) {
2166 		return;
2167 	}
2168 
2169 	*tagnum = 0;
2170 
2171 	id = strtoul(tagname, &ptr, 10);
2172 
2173 	/* Must be entirely numeric and in-range */
2174 	if (ptr && (*ptr != '\0')) {
2175 		return;
2176 	}
2177 
2178 	if ((id <= UINT16_MAX) && (id > 1)) {
2179 		*tagnum = (uint16_t)id;
2180 	}
2181 }
2182 
2183 /*
2184  * Print error messages to stderr for errnos and expected stmf errors.
2185  * This function should generally not be used for cases where the
2186  * calling code can generate a more detailed error message based on
2187  * the contextual knowledge of the meaning of specific errors.
2188  */
2189 static void
output_config_error(int error,char * msg)2190 output_config_error(int error, char *msg)
2191 {
2192 
2193 	if (msg) {
2194 		(void) fprintf(stderr, "%s: ", msg);
2195 	}
2196 
2197 	if (error & STMF_STATUS_ERROR) {
2198 		switch (error) {
2199 		case STMF_ERROR_PERM:
2200 			(void) fprintf(stderr, "%s",
2201 			    gettext("permission denied"));
2202 			break;
2203 		case STMF_ERROR_BUSY:
2204 			(void) fprintf(stderr, "%s",
2205 			    gettext("resource busy"));
2206 			break;
2207 		case STMF_ERROR_NOMEM:
2208 			(void) fprintf(stderr, "%s",
2209 			    gettext("out of memory"));
2210 			break;
2211 		case STMF_ERROR_SERVICE_NOT_FOUND:
2212 			(void) fprintf(stderr, "%s",
2213 			    gettext("STMF service not found"));
2214 			break;
2215 		case STMF_ERROR_SERVICE_DATA_VERSION:
2216 			(void) fprintf(stderr, "%s",
2217 			    gettext("STMF service version incorrect"));
2218 			break;
2219 		case STMF_ERROR_PROV_DATA_STALE:
2220 			(void) fprintf(stderr, "%s",
2221 			    gettext("Configuration changed during processing. "
2222 			    "Check the configuration, then retry this "
2223 			    "command if appropriate."));
2224 			break;
2225 		default:
2226 			(void) fprintf(stderr, "%s", gettext("unknown error"));
2227 			break;
2228 		}
2229 	} else {
2230 		char buf[80] = "";
2231 
2232 		(void) strerror_r(error, buf, sizeof (buf));
2233 		(void) fprintf(stderr, "%s", buf);
2234 	}
2235 
2236 	(void) fprintf(stderr, "\n");
2237 }
2238