1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2012 The FreeBSD Foundation
5 *
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 */
31
32#include <sys/cdefs.h>
33#include <sys/ioctl.h>
34#include <sys/param.h>
35#include <sys/linker.h>
36#include <assert.h>
37#include <ctype.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <limits.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45#include <libxo/xo.h>
46
47#include <iscsi_ioctl.h>
48#include "iscsictl.h"
49
50struct conf *
51conf_new(void)
52{
53	struct conf *conf;
54
55	conf = calloc(1, sizeof(*conf));
56	if (conf == NULL)
57		xo_err(1, "calloc");
58
59	TAILQ_INIT(&conf->conf_targets);
60
61	return (conf);
62}
63
64struct target *
65target_find(struct conf *conf, const char *nickname)
66{
67	struct target *targ;
68
69	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
70		if (targ->t_nickname != NULL &&
71		    strcasecmp(targ->t_nickname, nickname) == 0)
72			return (targ);
73	}
74
75	return (NULL);
76}
77
78struct target *
79target_new(struct conf *conf)
80{
81	struct target *targ;
82
83	targ = calloc(1, sizeof(*targ));
84	if (targ == NULL)
85		xo_err(1, "calloc");
86	targ->t_conf = conf;
87	targ->t_dscp = -1;
88	targ->t_pcp = -1;
89	targ->t_pingtimeout = -1;
90	targ->t_logintimeout = -1;
91	TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
92
93	return (targ);
94}
95
96void
97target_delete(struct target *targ)
98{
99
100	TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
101	free(targ);
102}
103
104static char *
105default_initiator_name(void)
106{
107	char *name;
108	size_t namelen;
109	int error;
110
111	namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
112
113	name = calloc(1, namelen + 1);
114	if (name == NULL)
115		xo_err(1, "calloc");
116	strcpy(name, DEFAULT_IQN);
117	error = gethostname(name + strlen(DEFAULT_IQN),
118	    namelen - strlen(DEFAULT_IQN));
119	if (error != 0)
120		xo_err(1, "gethostname");
121
122	return (name);
123}
124
125static bool
126valid_hex(const char ch)
127{
128	switch (ch) {
129	case '0':
130	case '1':
131	case '2':
132	case '3':
133	case '4':
134	case '5':
135	case '6':
136	case '7':
137	case '8':
138	case '9':
139	case 'a':
140	case 'A':
141	case 'b':
142	case 'B':
143	case 'c':
144	case 'C':
145	case 'd':
146	case 'D':
147	case 'e':
148	case 'E':
149	case 'f':
150	case 'F':
151		return (true);
152	default:
153		return (false);
154	}
155}
156
157int
158parse_enable(const char *enable)
159{
160	if (enable == NULL)
161		return (ENABLE_UNSPECIFIED);
162
163	if (strcasecmp(enable, "on") == 0 ||
164	    strcasecmp(enable, "yes") == 0)
165		return (ENABLE_ON);
166
167	if (strcasecmp(enable, "off") == 0 ||
168	    strcasecmp(enable, "no") == 0)
169		return (ENABLE_OFF);
170
171	return (ENABLE_UNSPECIFIED);
172}
173
174bool
175valid_iscsi_name(const char *name)
176{
177	int i;
178
179	if (strlen(name) >= MAX_NAME_LEN) {
180		xo_warnx("overlong name for \"%s\"; max length allowed "
181		    "by iSCSI specification is %d characters",
182		    name, MAX_NAME_LEN);
183		return (false);
184	}
185
186	/*
187	 * In the cases below, we don't return an error, just in case the admin
188	 * was right, and we're wrong.
189	 */
190	if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
191		for (i = strlen("iqn."); name[i] != '\0'; i++) {
192			/*
193			 * XXX: We should verify UTF-8 normalisation, as defined
194			 *      by 3.2.6.2: iSCSI Name Encoding.
195			 */
196			if (isalnum(name[i]))
197				continue;
198			if (name[i] == '-' || name[i] == '.' || name[i] == ':')
199				continue;
200			xo_warnx("invalid character \"%c\" in iSCSI name "
201			    "\"%s\"; allowed characters are letters, digits, "
202			    "'-', '.', and ':'", name[i], name);
203			break;
204		}
205		/*
206		 * XXX: Check more stuff: valid date and a valid reversed domain.
207		 */
208	} else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
209		if (strlen(name) != strlen("eui.") + 16)
210			xo_warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
211			    "should be followed by exactly 16 hexadecimal "
212			    "digits", name);
213		for (i = strlen("eui."); name[i] != '\0'; i++) {
214			if (!valid_hex(name[i])) {
215				xo_warnx("invalid character \"%c\" in iSCSI "
216				    "name \"%s\"; allowed characters are 1-9 "
217				    "and A-F", name[i], name);
218				break;
219			}
220		}
221	} else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
222		if (strlen(name) > strlen("naa.") + 32)
223			xo_warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
224			    "should be followed by at most 32 hexadecimal "
225			    "digits", name);
226		for (i = strlen("naa."); name[i] != '\0'; i++) {
227			if (!valid_hex(name[i])) {
228				xo_warnx("invalid character \"%c\" in ISCSI "
229				    "name \"%s\"; allowed characters are 1-9 "
230				    "and A-F", name[i], name);
231				break;
232			}
233		}
234	} else {
235		xo_warnx("invalid iSCSI name \"%s\"; should start with "
236		    "either \".iqn\", \"eui.\", or \"naa.\"",
237		    name);
238	}
239	return (true);
240}
241
242void
243conf_verify(struct conf *conf)
244{
245	struct target *targ;
246
247	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
248		assert(targ->t_nickname != NULL);
249		if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
250			targ->t_session_type = SESSION_TYPE_NORMAL;
251		if (targ->t_session_type == SESSION_TYPE_NORMAL &&
252		    targ->t_name == NULL)
253			xo_errx(1, "missing TargetName for target \"%s\"",
254			    targ->t_nickname);
255		if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
256		    targ->t_name != NULL)
257			xo_errx(1, "cannot specify TargetName for discovery "
258			    "sessions for target \"%s\"", targ->t_nickname);
259		if (targ->t_name != NULL) {
260			if (valid_iscsi_name(targ->t_name) == false)
261				xo_errx(1, "invalid target name \"%s\"",
262				    targ->t_name);
263		}
264		if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
265			targ->t_protocol = PROTOCOL_ISCSI;
266		if (targ->t_address == NULL)
267			xo_errx(1, "missing TargetAddress for target \"%s\"",
268			    targ->t_nickname);
269		if (targ->t_initiator_name == NULL)
270			targ->t_initiator_name = default_initiator_name();
271		if (valid_iscsi_name(targ->t_initiator_name) == false)
272			xo_errx(1, "invalid initiator name \"%s\"",
273			    targ->t_initiator_name);
274		if (targ->t_header_digest == DIGEST_UNSPECIFIED)
275			targ->t_header_digest = DIGEST_NONE;
276		if (targ->t_data_digest == DIGEST_UNSPECIFIED)
277			targ->t_data_digest = DIGEST_NONE;
278		if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
279			if (targ->t_user != NULL || targ->t_secret != NULL ||
280			    targ->t_mutual_user != NULL ||
281			    targ->t_mutual_secret != NULL)
282				targ->t_auth_method =
283				    AUTH_METHOD_CHAP;
284			else
285				targ->t_auth_method =
286				    AUTH_METHOD_NONE;
287		}
288		if (targ->t_auth_method == AUTH_METHOD_CHAP) {
289			if (targ->t_user == NULL) {
290				xo_errx(1, "missing chapIName for target \"%s\"",
291				    targ->t_nickname);
292			}
293			if (targ->t_secret == NULL)
294				xo_errx(1, "missing chapSecret for target \"%s\"",
295				    targ->t_nickname);
296			if (targ->t_mutual_user != NULL ||
297			    targ->t_mutual_secret != NULL) {
298				if (targ->t_mutual_user == NULL)
299					xo_errx(1, "missing tgtChapName for "
300					    "target \"%s\"", targ->t_nickname);
301				if (targ->t_mutual_secret == NULL)
302					xo_errx(1, "missing tgtChapSecret for "
303					    "target \"%s\"", targ->t_nickname);
304			}
305		}
306	}
307}
308
309static void
310conf_from_target(struct iscsi_session_conf *conf,
311    const struct target *targ)
312{
313	memset(conf, 0, sizeof(*conf));
314
315	/*
316	 * XXX: Check bounds and return error instead of silently truncating.
317	 */
318	if (targ->t_initiator_name != NULL)
319		strlcpy(conf->isc_initiator, targ->t_initiator_name,
320		    sizeof(conf->isc_initiator));
321	if (targ->t_initiator_address != NULL)
322		strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
323		    sizeof(conf->isc_initiator_addr));
324	if (targ->t_initiator_alias != NULL)
325		strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
326		    sizeof(conf->isc_initiator_alias));
327	if (targ->t_name != NULL)
328		strlcpy(conf->isc_target, targ->t_name,
329		    sizeof(conf->isc_target));
330	if (targ->t_address != NULL)
331		strlcpy(conf->isc_target_addr, targ->t_address,
332		    sizeof(conf->isc_target_addr));
333	if (targ->t_user != NULL)
334		strlcpy(conf->isc_user, targ->t_user,
335		    sizeof(conf->isc_user));
336	if (targ->t_secret != NULL)
337		strlcpy(conf->isc_secret, targ->t_secret,
338		    sizeof(conf->isc_secret));
339	if (targ->t_mutual_user != NULL)
340		strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
341		    sizeof(conf->isc_mutual_user));
342	if (targ->t_mutual_secret != NULL)
343		strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
344		    sizeof(conf->isc_mutual_secret));
345	if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
346		conf->isc_discovery = 1;
347	if (targ->t_enable != ENABLE_OFF)
348		conf->isc_enable = 1;
349	if (targ->t_protocol == PROTOCOL_ISER)
350		conf->isc_iser = 1;
351	if (targ->t_offload != NULL)
352		strlcpy(conf->isc_offload, targ->t_offload,
353		    sizeof(conf->isc_offload));
354	if (targ->t_header_digest == DIGEST_CRC32C)
355		conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
356	else
357		conf->isc_header_digest = ISCSI_DIGEST_NONE;
358	if (targ->t_data_digest == DIGEST_CRC32C)
359		conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
360	else
361		conf->isc_data_digest = ISCSI_DIGEST_NONE;
362	conf->isc_dscp = targ->t_dscp;
363	conf->isc_pcp = targ->t_pcp;
364	conf->isc_ping_timeout = targ->t_pingtimeout;
365	conf->isc_login_timeout = targ->t_logintimeout;
366}
367
368static int
369kernel_add(int iscsi_fd, const struct target *targ)
370{
371	struct iscsi_session_add isa;
372	int error;
373
374	memset(&isa, 0, sizeof(isa));
375	conf_from_target(&isa.isa_conf, targ);
376	error = ioctl(iscsi_fd, ISCSISADD, &isa);
377	if (error != 0)
378		xo_warn("ISCSISADD");
379	return (error);
380}
381
382static int
383kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
384{
385	struct iscsi_session_modify ism;
386	int error;
387
388	memset(&ism, 0, sizeof(ism));
389	ism.ism_session_id = session_id;
390	conf_from_target(&ism.ism_conf, targ);
391	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
392	if (error != 0)
393		xo_warn("ISCSISMODIFY");
394	return (error);
395}
396
397static void
398kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
399  const char *target_addr, const char *user, const char *secret, int enable)
400{
401	struct iscsi_session_state *states = NULL;
402	struct iscsi_session_state *state;
403	struct iscsi_session_conf *conf;
404	struct iscsi_session_list isl;
405	struct iscsi_session_modify ism;
406	unsigned int i, nentries = 1;
407	int error;
408
409	for (;;) {
410		states = realloc(states,
411		    nentries * sizeof(struct iscsi_session_state));
412		if (states == NULL)
413			xo_err(1, "realloc");
414
415		memset(&isl, 0, sizeof(isl));
416		isl.isl_nentries = nentries;
417		isl.isl_pstates = states;
418
419		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
420		if (error != 0 && errno == EMSGSIZE) {
421			nentries *= 4;
422			continue;
423		}
424		break;
425	}
426	if (error != 0)
427		xo_errx(1, "ISCSISLIST");
428
429	for (i = 0; i < isl.isl_nentries; i++) {
430		state = &states[i];
431
432		if (state->iss_id == session_id)
433			break;
434	}
435	if (i == isl.isl_nentries)
436		xo_errx(1, "session-id %u not found", session_id);
437
438	conf = &state->iss_conf;
439
440	if (target != NULL)
441		strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
442	if (target_addr != NULL)
443		strlcpy(conf->isc_target_addr, target_addr,
444		    sizeof(conf->isc_target_addr));
445	if (user != NULL)
446		strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
447	if (secret != NULL)
448		strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
449	if (enable == ENABLE_ON)
450		conf->isc_enable = 1;
451	else if (enable == ENABLE_OFF)
452		conf->isc_enable = 0;
453
454	memset(&ism, 0, sizeof(ism));
455	ism.ism_session_id = session_id;
456	memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
457	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
458	if (error != 0)
459		xo_warn("ISCSISMODIFY");
460}
461
462static int
463kernel_remove(int iscsi_fd, const struct target *targ)
464{
465	struct iscsi_session_remove isr;
466	int error;
467
468	memset(&isr, 0, sizeof(isr));
469	conf_from_target(&isr.isr_conf, targ);
470	error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
471	if (error != 0)
472		xo_warn("ISCSISREMOVE");
473	return (error);
474}
475
476/*
477 * XXX: Add filtering.
478 */
479static int
480kernel_list(int iscsi_fd, const struct target *targ __unused,
481    int verbose)
482{
483	struct iscsi_session_state *states = NULL;
484	const struct iscsi_session_state *state;
485	const struct iscsi_session_conf *conf;
486	struct iscsi_session_list isl;
487	unsigned int i, nentries = 1;
488	int error;
489
490	for (;;) {
491		states = realloc(states,
492		    nentries * sizeof(struct iscsi_session_state));
493		if (states == NULL)
494			xo_err(1, "realloc");
495
496		memset(&isl, 0, sizeof(isl));
497		isl.isl_nentries = nentries;
498		isl.isl_pstates = states;
499
500		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
501		if (error != 0 && errno == EMSGSIZE) {
502			nentries *= 4;
503			continue;
504		}
505		break;
506	}
507	if (error != 0) {
508		xo_warn("ISCSISLIST");
509		return (error);
510	}
511
512	if (verbose != 0) {
513		xo_open_list("session");
514		for (i = 0; i < isl.isl_nentries; i++) {
515			state = &states[i];
516			conf = &state->iss_conf;
517
518			xo_open_instance("session");
519
520			/*
521			 * Display-only modifier as this information
522			 * is also present within the 'session' container
523			 */
524			xo_emit("{L:/%-26s}{V:sessionId/%u}\n",
525			    "Session ID:", state->iss_id);
526
527			xo_open_container("initiator");
528			xo_emit("{L:/%-26s}{V:name/%s}\n",
529			    "Initiator name:", conf->isc_initiator);
530			xo_emit("{L:/%-26s}{V:portal/%s}\n",
531			    "Initiator portal:", conf->isc_initiator_addr);
532			xo_emit("{L:/%-26s}{V:alias/%s}\n",
533			    "Initiator alias:", conf->isc_initiator_alias);
534			xo_close_container("initiator");
535
536			xo_open_container("target");
537			xo_emit("{L:/%-26s}{V:name/%s}\n",
538			    "Target name:", conf->isc_target);
539			xo_emit("{L:/%-26s}{V:portal/%s}\n",
540			    "Target portal:", conf->isc_target_addr);
541			xo_emit("{L:/%-26s}{V:alias/%s}\n",
542			    "Target alias:", state->iss_target_alias);
543			if (conf->isc_dscp != -1)
544				xo_emit("{L:/%-26s}{V:dscp/0x%02x}\n",
545				    "Target DSCP:", conf->isc_dscp);
546			if (conf->isc_pcp != -1)
547				xo_emit("{L:/%-26s}{V:pcp/0x%02x}\n",
548				    "Target PCP:", conf->isc_pcp);
549			if (conf->isc_ping_timeout != -1)
550				xo_emit("{L:/%-26s}{V:PingTimeout/%d}\n",
551				    "Target PingTimeout:",
552				    conf->isc_ping_timeout);
553			if (conf->isc_login_timeout != -1)
554				xo_emit("{L:/%-26s}{V:LoginTimeout/%d}\n",
555				    "Target LoginTimeout:",
556				    conf->isc_login_timeout);
557			xo_close_container("target");
558
559			xo_open_container("auth");
560			xo_emit("{L:/%-26s}{V:user/%s}\n",
561			    "User:", conf->isc_user);
562			xo_emit("{L:/%-26s}{V:secret/%s}\n",
563			    "Secret:", conf->isc_secret);
564			xo_emit("{L:/%-26s}{V:mutualUser/%s}\n",
565			    "Mutual user:", conf->isc_mutual_user);
566			xo_emit("{L:/%-26s}{V:mutualSecret/%s}\n",
567			    "Mutual secret:", conf->isc_mutual_secret);
568			xo_close_container("auth");
569
570			xo_emit("{L:/%-26s}{V:type/%s}\n",
571			    "Session type:",
572			    conf->isc_discovery ? "Discovery" : "Normal");
573			xo_emit("{L:/%-26s}{V:enable/%s}\n",
574			    "Enable:",
575			    conf->isc_enable ? "Yes" : "No");
576			xo_emit("{L:/%-26s}{V:state/%s}\n",
577			    "Session state:",
578			    state->iss_connected ? "Connected" : "Disconnected");
579			xo_emit("{L:/%-26s}{V:failureReason/%s}\n",
580			    "Failure reason:", state->iss_reason);
581			xo_emit("{L:/%-26s}{V:headerDigest/%s}\n",
582			    "Header digest:",
583			    state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
584			    "CRC32C" : "None");
585			xo_emit("{L:/%-26s}{V:dataDigest/%s}\n",
586			    "Data digest:",
587			    state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
588			    "CRC32C" : "None");
589			xo_emit("{L:/%-26s}{V:recvDataSegmentLen/%d}\n",
590			    "MaxRecvDataSegmentLength:",
591			    state->iss_max_recv_data_segment_length);
592			xo_emit("{L:/%-26s}{V:sendDataSegmentLen/%d}\n",
593			    "MaxSendDataSegmentLength:",
594			    state->iss_max_send_data_segment_length);
595			xo_emit("{L:/%-26s}{V:maxBurstLen/%d}\n",
596			    "MaxBurstLen:", state->iss_max_burst_length);
597			xo_emit("{L:/%-26s}{V:firstBurstLen/%d}\n",
598			    "FirstBurstLen:", state->iss_first_burst_length);
599			xo_emit("{L:/%-26s}{V:immediateData/%s}\n",
600			    "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
601			xo_emit("{L:/%-26s}{V:iSER/%s}\n",
602			    "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
603			xo_emit("{L:/%-26s}{V:offloadDriver/%s}\n",
604			    "Offload driver:", state->iss_offload);
605			xo_emit("{L:/%-26s}",
606			    "Device nodes:");
607			print_periphs(state->iss_id);
608			xo_emit("\n\n");
609			xo_close_instance("session");
610		}
611		xo_close_list("session");
612	} else {
613		xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
614		    "Target name", "Target portal", "State");
615
616		if (isl.isl_nentries != 0)
617			xo_open_list("session");
618		for (i = 0; i < isl.isl_nentries; i++) {
619
620			state = &states[i];
621			conf = &state->iss_conf;
622
623			xo_open_instance("session");
624			xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
625			    conf->isc_target, conf->isc_target_addr);
626
627			if (state->iss_reason[0] != '\0' &&
628			    conf->isc_enable != 0) {
629				xo_emit("{V:state/%s}\n", state->iss_reason);
630			} else {
631				if (conf->isc_discovery) {
632					xo_emit("{V:state}\n", "Discovery");
633				} else if (conf->isc_enable == 0) {
634					xo_emit("{V:state}\n", "Disabled");
635				} else if (state->iss_connected) {
636					xo_emit("{V:state}: ", "Connected");
637					print_periphs(state->iss_id);
638					xo_emit("\n");
639				} else {
640					xo_emit("{V:state}\n", "Disconnected");
641				}
642			}
643			xo_close_instance("session");
644		}
645		if (isl.isl_nentries != 0)
646			xo_close_list("session");
647	}
648
649	return (0);
650}
651
652static int
653kernel_wait(int iscsi_fd, int timeout)
654{
655	struct iscsi_session_state *states = NULL;
656	const struct iscsi_session_state *state;
657	struct iscsi_session_list isl;
658	unsigned int i, nentries = 1;
659	bool all_connected;
660	int error;
661
662	for (;;) {
663		for (;;) {
664			states = realloc(states,
665			    nentries * sizeof(struct iscsi_session_state));
666			if (states == NULL)
667				xo_err(1, "realloc");
668
669			memset(&isl, 0, sizeof(isl));
670			isl.isl_nentries = nentries;
671			isl.isl_pstates = states;
672
673			error = ioctl(iscsi_fd, ISCSISLIST, &isl);
674			if (error != 0 && errno == EMSGSIZE) {
675				nentries *= 4;
676				continue;
677			}
678			break;
679		}
680		if (error != 0) {
681			xo_warn("ISCSISLIST");
682			return (error);
683		}
684
685		all_connected = true;
686		for (i = 0; i < isl.isl_nentries; i++) {
687			state = &states[i];
688
689			if (!state->iss_connected) {
690				all_connected = false;
691				break;
692			}
693		}
694
695		if (all_connected)
696			return (0);
697
698		sleep(1);
699
700		if (timeout > 0) {
701			timeout--;
702			if (timeout == 0)
703				return (1);
704		}
705	}
706}
707
708static void
709usage(void)
710{
711
712	fprintf(stderr, "usage: iscsictl -A -p portal -t target "
713	    "[-u user -s secret] [-w timeout] [-e on | off]\n");
714	fprintf(stderr, "       iscsictl -A -d discovery-host "
715	    "[-u user -s secret] [-e on | off]\n");
716	fprintf(stderr, "       iscsictl -A -a [-c path]\n");
717	fprintf(stderr, "       iscsictl -A -n nickname [-c path]\n");
718	fprintf(stderr, "       iscsictl -M -i session-id [-p portal] "
719	    "[-t target] [-u user] [-s secret] [-e on | off]\n");
720	fprintf(stderr, "       iscsictl -M -i session-id -n nickname "
721	    "[-c path]\n");
722	fprintf(stderr, "       iscsictl -R [-p portal] [-t target]\n");
723	fprintf(stderr, "       iscsictl -R -a\n");
724	fprintf(stderr, "       iscsictl -R -n nickname [-c path]\n");
725	fprintf(stderr, "       iscsictl -L [-v] [-w timeout]\n");
726	exit(1);
727}
728
729int
730main(int argc, char **argv)
731{
732	int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0,
733	    rflag = 0, vflag = 0;
734	const char *conf_path = DEFAULT_CONFIG_PATH;
735	char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
736	    *target = NULL, *user = NULL, *secret = NULL;
737	int timeout = -1, enable = ENABLE_UNSPECIFIED;
738	long long session_id = -1;
739	char *end;
740	int ch, error, iscsi_fd, retval, saved_errno;
741	int failed = 0;
742	struct conf *conf;
743	struct target *targ;
744
745	argc = xo_parse_args(argc, argv);
746        if (argc < 0)
747                exit(1);
748
749	xo_open_container("iscsictl");
750
751	while ((ch = getopt(argc, argv, "AMRLac:d:e:i:n:p:rt:u:s:vw:")) != -1) {
752		switch (ch) {
753		case 'A':
754			Aflag = 1;
755			break;
756		case 'M':
757			Mflag = 1;
758			break;
759		case 'R':
760			Rflag = 1;
761			break;
762		case 'L':
763			Lflag = 1;
764			break;
765		case 'a':
766			aflag = 1;
767			break;
768		case 'c':
769			conf_path = optarg;
770			break;
771		case 'd':
772			discovery_host = optarg;
773			break;
774		case 'e':
775			enable = parse_enable(optarg);
776			if (enable == ENABLE_UNSPECIFIED) {
777				xo_errx(1, "invalid argument to -e, "
778				    "must be either \"on\" or \"off\"");
779			}
780			break;
781		case 'i':
782			session_id = strtol(optarg, &end, 10);
783			if ((size_t)(end - optarg) != strlen(optarg))
784				xo_errx(1, "trailing characters after session-id");
785			if (session_id < 0)
786				xo_errx(1, "session-id cannot be negative");
787			if (session_id > UINT_MAX)
788				xo_errx(1, "session-id cannot be greater than %u",
789				    UINT_MAX);
790			break;
791		case 'n':
792			nickname = optarg;
793			break;
794		case 'p':
795			portal = optarg;
796			break;
797		case 'r':
798			rflag = 1;
799			break;
800		case 't':
801			target = optarg;
802			break;
803		case 'u':
804			user = optarg;
805			break;
806		case 's':
807			secret = optarg;
808			break;
809		case 'v':
810			vflag = 1;
811			break;
812		case 'w':
813			timeout = strtol(optarg, &end, 10);
814			if ((size_t)(end - optarg) != strlen(optarg))
815				xo_errx(1, "trailing characters after timeout");
816			if (timeout < 0)
817				xo_errx(1, "timeout cannot be negative");
818			break;
819		case '?':
820		default:
821			usage();
822		}
823	}
824	argc -= optind;
825	if (argc != 0)
826		usage();
827
828	if (Aflag + Mflag + Rflag + Lflag == 0)
829		Lflag = 1;
830	if (Aflag + Mflag + Rflag + Lflag > 1)
831		xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
832
833	/*
834	 * Note that we ignore unnecessary/inapplicable "-c" flag; so that
835	 * people can do something like "alias ISCSICTL="iscsictl -c path"
836	 * in shell scripts.
837	 */
838	if (Aflag != 0) {
839		if (aflag != 0) {
840			if (enable != ENABLE_UNSPECIFIED)
841				xo_errx(1, "-a and -e are mutually exclusive");
842			if (portal != NULL)
843				xo_errx(1, "-a and -p are mutually exclusive");
844			if (target != NULL)
845				xo_errx(1, "-a and -t are mutually exclusive");
846			if (user != NULL)
847				xo_errx(1, "-a and -u are mutually exclusive");
848			if (secret != NULL)
849				xo_errx(1, "-a and -s are mutually exclusive");
850			if (nickname != NULL)
851				xo_errx(1, "-a and -n are mutually exclusive");
852			if (discovery_host != NULL)
853				xo_errx(1, "-a and -d are mutually exclusive");
854			if (rflag != 0)
855				xo_errx(1, "-a and -r are mutually exclusive");
856		} else if (nickname != NULL) {
857			if (enable != ENABLE_UNSPECIFIED)
858				xo_errx(1, "-n and -e are mutually exclusive");
859			if (portal != NULL)
860				xo_errx(1, "-n and -p are mutually exclusive");
861			if (target != NULL)
862				xo_errx(1, "-n and -t are mutually exclusive");
863			if (user != NULL)
864				xo_errx(1, "-n and -u are mutually exclusive");
865			if (secret != NULL)
866				xo_errx(1, "-n and -s are mutually exclusive");
867			if (discovery_host != NULL)
868				xo_errx(1, "-n and -d are mutually exclusive");
869			if (rflag != 0)
870				xo_errx(1, "-n and -r are mutually exclusive");
871		} else if (discovery_host != NULL) {
872			if (portal != NULL)
873				xo_errx(1, "-d and -p are mutually exclusive");
874			if (target != NULL)
875				xo_errx(1, "-d and -t are mutually exclusive");
876		} else {
877			if (target == NULL && portal == NULL)
878				xo_errx(1, "must specify -a, -n or -t/-p");
879
880			if (target != NULL && portal == NULL)
881				xo_errx(1, "-t must always be used with -p");
882			if (portal != NULL && target == NULL)
883				xo_errx(1, "-p must always be used with -t");
884		}
885
886		if (user != NULL && secret == NULL)
887			xo_errx(1, "-u must always be used with -s");
888		if (secret != NULL && user == NULL)
889			xo_errx(1, "-s must always be used with -u");
890
891		if (session_id != -1)
892			xo_errx(1, "-i cannot be used with -A");
893		if (vflag != 0)
894			xo_errx(1, "-v cannot be used with -A");
895
896	} else if (Mflag != 0) {
897		if (session_id == -1)
898			xo_errx(1, "-M requires -i");
899
900		if (nickname != NULL) {
901			if (enable != ENABLE_UNSPECIFIED)
902				xo_errx(1, "-n and -e are mutually exclusive");
903			if (portal != NULL)
904				xo_errx(1, "-n and -p are mutually exclusive");
905			if (target != NULL)
906				xo_errx(1, "-n and -t are mutually exclusive");
907			if (user != NULL)
908				xo_errx(1, "-n and -u are mutually exclusive");
909			if (secret != NULL)
910				xo_errx(1, "-n and -s are mutually exclusive");
911		}
912
913		if (aflag != 0)
914			xo_errx(1, "-a cannot be used with -M");
915		if (discovery_host != NULL)
916			xo_errx(1, "-d cannot be used with -M");
917		if (rflag != 0)
918			xo_errx(1, "-r cannot be used with -M");
919		if (vflag != 0)
920			xo_errx(1, "-v cannot be used with -M");
921		if (timeout != -1)
922			xo_errx(1, "-w cannot be used with -M");
923
924	} else if (Rflag != 0) {
925		if (aflag != 0) {
926			if (portal != NULL)
927				xo_errx(1, "-a and -p are mutually exclusive");
928			if (target != NULL)
929				xo_errx(1, "-a and -t are mutually exclusive");
930			if (nickname != NULL)
931				xo_errx(1, "-a and -n are mutually exclusive");
932		} else if (nickname != NULL) {
933			if (portal != NULL)
934				xo_errx(1, "-n and -p are mutually exclusive");
935			if (target != NULL)
936				xo_errx(1, "-n and -t are mutually exclusive");
937		} else if (target == NULL && portal == NULL) {
938			xo_errx(1, "must specify either -a, -n, -t, or -p");
939		}
940
941		if (discovery_host != NULL)
942			xo_errx(1, "-d cannot be used with -R");
943		if (enable != ENABLE_UNSPECIFIED)
944			xo_errx(1, "-e cannot be used with -R");
945		if (session_id != -1)
946			xo_errx(1, "-i cannot be used with -R");
947		if (rflag != 0)
948			xo_errx(1, "-r cannot be used with -R");
949		if (user != NULL)
950			xo_errx(1, "-u cannot be used with -R");
951		if (secret != NULL)
952			xo_errx(1, "-s cannot be used with -R");
953		if (vflag != 0)
954			xo_errx(1, "-v cannot be used with -R");
955		if (timeout != -1)
956			xo_errx(1, "-w cannot be used with -R");
957
958	} else {
959		assert(Lflag != 0);
960
961		if (discovery_host != NULL)
962			xo_errx(1, "-d cannot be used with -L");
963		if (session_id != -1)
964			xo_errx(1, "-i cannot be used with -L");
965		if (nickname != NULL)
966			xo_errx(1, "-n cannot be used with -L");
967		if (portal != NULL)
968			xo_errx(1, "-p cannot be used with -L");
969		if (rflag != 0)
970			xo_errx(1, "-r cannot be used with -L");
971		if (target != NULL)
972			xo_errx(1, "-t cannot be used with -L");
973		if (user != NULL)
974			xo_errx(1, "-u cannot be used with -L");
975		if (secret != NULL)
976			xo_errx(1, "-s cannot be used with -L");
977	}
978
979	iscsi_fd = open(ISCSI_PATH, O_RDWR);
980	if (iscsi_fd < 0 && errno == ENOENT) {
981		saved_errno = errno;
982		retval = kldload("iscsi");
983		if (retval != -1)
984			iscsi_fd = open(ISCSI_PATH, O_RDWR);
985		else
986			errno = saved_errno;
987	}
988	if (iscsi_fd < 0)
989		xo_err(1, "failed to open %s", ISCSI_PATH);
990
991	if (Aflag != 0 && aflag != 0) {
992		conf = conf_new_from_file(conf_path);
993
994		TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
995			failed += kernel_add(iscsi_fd, targ);
996	} else if (nickname != NULL) {
997		conf = conf_new_from_file(conf_path);
998		targ = target_find(conf, nickname);
999		if (targ == NULL)
1000			xo_errx(1, "target %s not found in %s",
1001			    nickname, conf_path);
1002
1003		if (Aflag != 0)
1004			failed += kernel_add(iscsi_fd, targ);
1005		else if (Mflag != 0)
1006			failed += kernel_modify(iscsi_fd, session_id, targ);
1007		else if (Rflag != 0)
1008			failed += kernel_remove(iscsi_fd, targ);
1009		else
1010			failed += kernel_list(iscsi_fd, targ, vflag);
1011	} else if (Mflag != 0) {
1012		kernel_modify_some(iscsi_fd, session_id, target, portal,
1013		    user, secret, enable);
1014	} else {
1015		if (Aflag != 0 && target != NULL) {
1016			if (valid_iscsi_name(target) == false)
1017				xo_errx(1, "invalid target name \"%s\"", target);
1018		}
1019		conf = conf_new();
1020		targ = target_new(conf);
1021		targ->t_initiator_name = default_initiator_name();
1022		targ->t_header_digest = DIGEST_NONE;
1023		targ->t_data_digest = DIGEST_NONE;
1024		targ->t_name = target;
1025		if (discovery_host != NULL) {
1026			targ->t_session_type = SESSION_TYPE_DISCOVERY;
1027			targ->t_address = discovery_host;
1028		} else {
1029			targ->t_session_type = SESSION_TYPE_NORMAL;
1030			targ->t_address = portal;
1031		}
1032		targ->t_enable = enable;
1033		if (rflag != 0)
1034			targ->t_protocol = PROTOCOL_ISER;
1035		targ->t_user = user;
1036		targ->t_secret = secret;
1037
1038		if (Aflag != 0)
1039			failed += kernel_add(iscsi_fd, targ);
1040		else if (Rflag != 0)
1041			failed += kernel_remove(iscsi_fd, targ);
1042		else
1043			failed += kernel_list(iscsi_fd, targ, vflag);
1044	}
1045
1046	if (timeout != -1)
1047		failed += kernel_wait(iscsi_fd, timeout);
1048
1049	error = close(iscsi_fd);
1050	if (error != 0)
1051		xo_err(1, "close");
1052
1053	xo_close_container("iscsictl");
1054	xo_finish();
1055
1056	if (failed != 0)
1057		return (1);
1058
1059	return (0);
1060}
1061