xref: /openbsd-src/usr.sbin/iscsid/initiator.c (revision 2224f3a16d1f18cce3c29a4d93c68c31af83a07e)
1*2224f3a1Sclaudio /*	$OpenBSD: initiator.c,v 1.21 2025/01/28 20:41:44 claudio Exp $ */
2bde1ae23Sclaudio 
3bde1ae23Sclaudio /*
4bde1ae23Sclaudio  * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5bde1ae23Sclaudio  *
6bde1ae23Sclaudio  * Permission to use, copy, modify, and distribute this software for any
7bde1ae23Sclaudio  * purpose with or without fee is hereby granted, provided that the above
8bde1ae23Sclaudio  * copyright notice and this permission notice appear in all copies.
9bde1ae23Sclaudio  *
10bde1ae23Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11bde1ae23Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12bde1ae23Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13bde1ae23Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14bde1ae23Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15bde1ae23Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16bde1ae23Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17bde1ae23Sclaudio  */
18bde1ae23Sclaudio 
19bde1ae23Sclaudio #include <sys/types.h>
20bde1ae23Sclaudio #include <sys/queue.h>
21bde1ae23Sclaudio #include <sys/socket.h>
22bde1ae23Sclaudio #include <sys/uio.h>
23bde1ae23Sclaudio 
24bde1ae23Sclaudio #include <scsi/iscsi.h>
25bde1ae23Sclaudio 
26bde1ae23Sclaudio #include <event.h>
27bde1ae23Sclaudio #include <stdio.h>
28bde1ae23Sclaudio #include <stdlib.h>
29bde1ae23Sclaudio #include <string.h>
30bde1ae23Sclaudio #include <unistd.h>
31ea0867c0Sderaadt #include <limits.h>
32bde1ae23Sclaudio 
33bde1ae23Sclaudio #include "iscsid.h"
34bde1ae23Sclaudio #include "log.h"
35bde1ae23Sclaudio 
367b833938Sclaudio static struct initiator *initiator;
37bde1ae23Sclaudio 
384125a3c4Sclaudio struct task_login {
394125a3c4Sclaudio 	struct task		 task;
404125a3c4Sclaudio 	struct connection	*c;
414125a3c4Sclaudio 	u_int16_t		 tsih;
424125a3c4Sclaudio 	u_int8_t		 stage;
434125a3c4Sclaudio };
444125a3c4Sclaudio 
454125a3c4Sclaudio struct task_logout {
464125a3c4Sclaudio 	struct task		 task;
474125a3c4Sclaudio 	struct connection	*c;
484125a3c4Sclaudio 	u_int8_t		 reason;
494125a3c4Sclaudio };
504125a3c4Sclaudio 
51*2224f3a1Sclaudio int		 conn_is_leading(struct connection *);
524125a3c4Sclaudio struct kvp	*initiator_login_kvp(struct connection *, u_int8_t);
534125a3c4Sclaudio struct pdu	*initiator_login_build(struct connection *,
544125a3c4Sclaudio 		    struct task_login *);
554125a3c4Sclaudio struct pdu	*initiator_text_build(struct task *, struct session *,
564125a3c4Sclaudio 		    struct kvp *);
574125a3c4Sclaudio 
584125a3c4Sclaudio void	initiator_login_cb(struct connection *, void *, struct pdu *);
594125a3c4Sclaudio void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
604125a3c4Sclaudio void	initiator_logout_cb(struct connection *, void *, struct pdu *);
614125a3c4Sclaudio 
624125a3c4Sclaudio struct session_params		initiator_sess_defaults;
634125a3c4Sclaudio struct connection_params	initiator_conn_defaults;
64bde1ae23Sclaudio 
657b833938Sclaudio void
66bde1ae23Sclaudio initiator_init(void)
67bde1ae23Sclaudio {
68bde1ae23Sclaudio 	if (!(initiator = calloc(1, sizeof(*initiator))))
69bde1ae23Sclaudio 		fatal("initiator_init");
70bde1ae23Sclaudio 
71bde1ae23Sclaudio 	initiator->config.isid_base =
72bde1ae23Sclaudio 	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
73bde1ae23Sclaudio 	initiator->config.isid_qual = arc4random_uniform(0xffff);
74bde1ae23Sclaudio 	TAILQ_INIT(&initiator->sessions);
754125a3c4Sclaudio 
764125a3c4Sclaudio 	/* initialize initiator defaults */
774125a3c4Sclaudio 	initiator_sess_defaults = iscsi_sess_defaults;
784125a3c4Sclaudio 	initiator_conn_defaults = iscsi_conn_defaults;
7981d087b1Sclaudio 	initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS;
804125a3c4Sclaudio 	initiator_conn_defaults.MaxRecvDataSegmentLength = 65536;
81bde1ae23Sclaudio }
82bde1ae23Sclaudio 
83bde1ae23Sclaudio void
847b833938Sclaudio initiator_cleanup(void)
85bde1ae23Sclaudio {
86bde1ae23Sclaudio 	struct session *s;
87bde1ae23Sclaudio 
887b833938Sclaudio 	while ((s = TAILQ_FIRST(&initiator->sessions)) != NULL) {
897b833938Sclaudio 		TAILQ_REMOVE(&initiator->sessions, s, entry);
90cb408c6cSclaudio 		session_cleanup(s);
91bde1ae23Sclaudio 	}
92bde1ae23Sclaudio 	free(initiator);
93bde1ae23Sclaudio }
94bde1ae23Sclaudio 
9512bd4d7fSclaudio void
967b833938Sclaudio initiator_set_config(struct initiator_config *ic)
977b833938Sclaudio {
987b833938Sclaudio 	initiator->config = *ic;
997b833938Sclaudio }
1007b833938Sclaudio 
1017b833938Sclaudio struct initiator_config *
1027b833938Sclaudio initiator_get_config(void)
1037b833938Sclaudio {
1047b833938Sclaudio 	return &initiator->config;
1057b833938Sclaudio }
1067b833938Sclaudio 
1077b833938Sclaudio void
1087b833938Sclaudio initiator_shutdown(void)
10912bd4d7fSclaudio {
11012bd4d7fSclaudio 	struct session *s;
11112bd4d7fSclaudio 
11212bd4d7fSclaudio 	log_debug("initiator_shutdown: going down");
11312bd4d7fSclaudio 
11412bd4d7fSclaudio 	TAILQ_FOREACH(s, &initiator->sessions, entry)
11512bd4d7fSclaudio 		session_shutdown(s);
11612bd4d7fSclaudio }
11712bd4d7fSclaudio 
11812bd4d7fSclaudio int
1197b833938Sclaudio initiator_isdown(void)
12012bd4d7fSclaudio {
12112bd4d7fSclaudio 	struct session *s;
12212bd4d7fSclaudio 	int inprogres = 0;
12312bd4d7fSclaudio 
12412bd4d7fSclaudio 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
12512bd4d7fSclaudio 		if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE))
12612bd4d7fSclaudio 			inprogres = 1;
12712bd4d7fSclaudio 	}
12812bd4d7fSclaudio 	return !inprogres;
12912bd4d7fSclaudio }
13012bd4d7fSclaudio 
131bde1ae23Sclaudio struct session *
1327b833938Sclaudio initiator_new_session(u_int8_t st)
1337b833938Sclaudio {
1347b833938Sclaudio 	struct session *s;
1357b833938Sclaudio 
1367b833938Sclaudio 	if (!(s = calloc(1, sizeof(*s))))
1377b833938Sclaudio 		return NULL;
1387b833938Sclaudio 
1397b833938Sclaudio 	/* use the same qualifier unless there is a conflict */
1407b833938Sclaudio 	s->isid_base = initiator->config.isid_base;
1417b833938Sclaudio 	s->isid_qual = initiator->config.isid_qual;
1427b833938Sclaudio 	s->cmdseqnum = arc4random();
1437b833938Sclaudio 	s->itt = arc4random();
1447b833938Sclaudio 	s->state = SESS_INIT;
1457b833938Sclaudio 
1467b833938Sclaudio 	s->sev.sess = s;
1477b833938Sclaudio 	evtimer_set(&s->sev.ev, session_fsm_callback, &s->sev);
1487b833938Sclaudio 
1497b833938Sclaudio 	if (st == SESSION_TYPE_DISCOVERY)
1507b833938Sclaudio 		s->target = 0;
1517b833938Sclaudio 	else
1527b833938Sclaudio 		s->target = initiator->target++;
1537b833938Sclaudio 
1547b833938Sclaudio 	TAILQ_INIT(&s->connections);
1557b833938Sclaudio 	TAILQ_INIT(&s->tasks);
1567b833938Sclaudio 
1577b833938Sclaudio 	TAILQ_INSERT_HEAD(&initiator->sessions, s, entry);
1587b833938Sclaudio 
1597b833938Sclaudio 	return s;
1607b833938Sclaudio }
1617b833938Sclaudio 
1627b833938Sclaudio struct session *
1637b833938Sclaudio initiator_find_session(char *name)
1647b833938Sclaudio {
1657b833938Sclaudio 	struct session *s;
1667b833938Sclaudio 
1677b833938Sclaudio 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
1687b833938Sclaudio 		if (strcmp(s->config.SessionName, name) == 0)
1697b833938Sclaudio 			return s;
1707b833938Sclaudio 	}
1717b833938Sclaudio 	return NULL;
1727b833938Sclaudio }
1737b833938Sclaudio 
1747b833938Sclaudio struct session *
175bde1ae23Sclaudio initiator_t2s(u_int target)
176bde1ae23Sclaudio {
177bde1ae23Sclaudio 	struct session *s;
178bde1ae23Sclaudio 
179bde1ae23Sclaudio 	TAILQ_FOREACH(s, &initiator->sessions, entry) {
180bde1ae23Sclaudio 		if (s->target == target)
181bde1ae23Sclaudio 			return s;
182bde1ae23Sclaudio 	}
183bde1ae23Sclaudio 	return NULL;
184bde1ae23Sclaudio }
185bde1ae23Sclaudio 
1867b833938Sclaudio struct session_head *
1877b833938Sclaudio initiator_get_sessions(void)
1887b833938Sclaudio {
1897b833938Sclaudio 	return &initiator->sessions;
1907b833938Sclaudio }
1917b833938Sclaudio 
192bde1ae23Sclaudio void
193bde1ae23Sclaudio initiator_login(struct connection *c)
194bde1ae23Sclaudio {
195bde1ae23Sclaudio 	struct task_login *tl;
196bde1ae23Sclaudio 	struct pdu *p;
197bde1ae23Sclaudio 
198bde1ae23Sclaudio 	if (!(tl = calloc(1, sizeof(*tl)))) {
199bde1ae23Sclaudio 		log_warn("initiator_login");
200bde1ae23Sclaudio 		conn_fail(c);
201bde1ae23Sclaudio 		return;
202bde1ae23Sclaudio 	}
203bde1ae23Sclaudio 	tl->c = c;
204bde1ae23Sclaudio 	tl->stage = ISCSI_LOGIN_STG_SECNEG;
205bde1ae23Sclaudio 
2064125a3c4Sclaudio 	if (!(p = initiator_login_build(c, tl))) {
207cb408c6cSclaudio 		log_warn("initiator_login_build failed");
208bde1ae23Sclaudio 		free(tl);
209bde1ae23Sclaudio 		conn_fail(c);
210bde1ae23Sclaudio 		return;
211bde1ae23Sclaudio 	}
212bde1ae23Sclaudio 
213cb408c6cSclaudio 	task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL);
214bde1ae23Sclaudio 	task_pdu_add(&tl->task, p);
215cb408c6cSclaudio 	conn_task_issue(c, &tl->task);
216bde1ae23Sclaudio }
217bde1ae23Sclaudio 
218bde1ae23Sclaudio void
219bde1ae23Sclaudio initiator_discovery(struct session *s)
220bde1ae23Sclaudio {
221bde1ae23Sclaudio 	struct task *t;
222bde1ae23Sclaudio 	struct pdu *p;
223bde1ae23Sclaudio 	struct kvp kvp[] = {
224bde1ae23Sclaudio 		{ "SendTargets", "All" },
225bde1ae23Sclaudio 		{ NULL, NULL }
226bde1ae23Sclaudio 	};
227bde1ae23Sclaudio 
228bde1ae23Sclaudio 	if (!(t = calloc(1, sizeof(*t)))) {
229bde1ae23Sclaudio 		log_warn("initiator_discovery");
2304125a3c4Sclaudio 		/* XXX sess_fail(c); */
231bde1ae23Sclaudio 		return;
232bde1ae23Sclaudio 	}
233bde1ae23Sclaudio 
234bde1ae23Sclaudio 	if (!(p = initiator_text_build(t, s, kvp))) {
235bde1ae23Sclaudio 		log_warnx("initiator_text_build failed");
23617fa8594Sclaudio 		free(t);
2374125a3c4Sclaudio 		/* XXX sess_fail(c); */
238bde1ae23Sclaudio 		return;
239bde1ae23Sclaudio 	}
240bde1ae23Sclaudio 
241cb408c6cSclaudio 	task_init(t, s, 0, t, initiator_discovery_cb, NULL);
242bde1ae23Sclaudio 	task_pdu_add(t, p);
243bde1ae23Sclaudio 	session_task_issue(s, t);
244bde1ae23Sclaudio }
245bde1ae23Sclaudio 
246cb408c6cSclaudio void
2474125a3c4Sclaudio initiator_logout(struct session *s, struct connection *c, u_int8_t reason)
2484125a3c4Sclaudio {
2494125a3c4Sclaudio 	struct task_logout *tl;
2504125a3c4Sclaudio 	struct pdu *p;
2514125a3c4Sclaudio 	struct iscsi_pdu_logout_request *loreq;
2524125a3c4Sclaudio 
2534125a3c4Sclaudio 	if (!(tl = calloc(1, sizeof(*tl)))) {
2544125a3c4Sclaudio 		log_warn("initiator_logout");
2554125a3c4Sclaudio 		/* XXX sess_fail */
2564125a3c4Sclaudio 		return;
2574125a3c4Sclaudio 	}
2584125a3c4Sclaudio 	tl->c = c;
2594125a3c4Sclaudio 	tl->reason = reason;
2604125a3c4Sclaudio 
2614125a3c4Sclaudio 	if (!(p = pdu_new())) {
2624125a3c4Sclaudio 		log_warn("initiator_logout");
2634125a3c4Sclaudio 		/* XXX sess_fail */
2644125a3c4Sclaudio 		free(tl);
2654125a3c4Sclaudio 		return;
2664125a3c4Sclaudio 	}
2674125a3c4Sclaudio 	if (!(loreq = pdu_gethdr(p))) {
2684125a3c4Sclaudio 		log_warn("initiator_logout");
2694125a3c4Sclaudio 		/* XXX sess_fail */
2704125a3c4Sclaudio 		pdu_free(p);
2714125a3c4Sclaudio 		free(tl);
2724125a3c4Sclaudio 		return;
2734125a3c4Sclaudio 	}
2744125a3c4Sclaudio 
2754125a3c4Sclaudio 	loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
2764125a3c4Sclaudio 	loreq->flags = ISCSI_LOGOUT_F | reason;
2774125a3c4Sclaudio 	if (reason != ISCSI_LOGOUT_CLOSE_SESS)
2784125a3c4Sclaudio 		loreq->cid = c->cid;
2794125a3c4Sclaudio 
2804125a3c4Sclaudio 	task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL);
2814125a3c4Sclaudio 	task_pdu_add(&tl->task, p);
2824125a3c4Sclaudio 	if (c && (c->state & CONN_RUNNING))
2834125a3c4Sclaudio 		conn_task_issue(c, &tl->task);
2844125a3c4Sclaudio 	else
2854125a3c4Sclaudio 		session_logout_issue(s, &tl->task);
2864125a3c4Sclaudio }
2874125a3c4Sclaudio 
2884125a3c4Sclaudio void
2894125a3c4Sclaudio initiator_nop_in_imm(struct connection *c, struct pdu *p)
2904125a3c4Sclaudio {
2914125a3c4Sclaudio 	struct iscsi_pdu_nop_in *nopin;
2924125a3c4Sclaudio 	struct task *t;
2934125a3c4Sclaudio 
2944125a3c4Sclaudio 	/* fixup NOP-IN to make it a NOP-OUT */
2954125a3c4Sclaudio 	nopin = pdu_getbuf(p, NULL, PDU_HEADER);
2964125a3c4Sclaudio 	nopin->maxcmdsn = 0;
2974125a3c4Sclaudio 	nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
2984125a3c4Sclaudio 
2994125a3c4Sclaudio 	/* and schedule an immediate task */
3004125a3c4Sclaudio 	if (!(t = calloc(1, sizeof(*t)))) {
3014125a3c4Sclaudio 		log_warn("initiator_nop_in_imm");
3024125a3c4Sclaudio 		pdu_free(p);
3034125a3c4Sclaudio 		return;
3044125a3c4Sclaudio 	}
3054125a3c4Sclaudio 
3064125a3c4Sclaudio 	task_init(t, c->session, 1, NULL, NULL, NULL);
3074125a3c4Sclaudio 	t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
3084125a3c4Sclaudio 	task_pdu_add(t, p);
3094125a3c4Sclaudio 	conn_task_issue(c, t);
3104125a3c4Sclaudio }
3114125a3c4Sclaudio 
312*2224f3a1Sclaudio int
313*2224f3a1Sclaudio conn_is_leading(struct connection *c)
314*2224f3a1Sclaudio {
315*2224f3a1Sclaudio 	return c == TAILQ_FIRST(&c->session->connections);
316*2224f3a1Sclaudio }
317*2224f3a1Sclaudio 
318*2224f3a1Sclaudio #define MINE_NOT_DEFAULT(c, k) ((c)->mine.k != iscsi_conn_defaults.k)
319*2224f3a1Sclaudio 
3204125a3c4Sclaudio struct kvp *
3214125a3c4Sclaudio initiator_login_kvp(struct connection *c, u_int8_t stage)
3224125a3c4Sclaudio {
323*2224f3a1Sclaudio 	struct kvp *kvp = NULL;
324*2224f3a1Sclaudio 	size_t i = 0, len;
325*2224f3a1Sclaudio 	const char *discovery[] = {"SessionType", "InitiatorName",
326*2224f3a1Sclaudio 	    "AuthMethod", NULL};
327*2224f3a1Sclaudio 	const char *leading_only[] = {"MaxConnections", "InitialR2T",
328*2224f3a1Sclaudio 	    "ImmediateData", "MaxBurstLength", "FirstBurstLength",
329*2224f3a1Sclaudio 	    "DefaultTime2Wait", "DefaultTime2Retain", "MaxOutstandingR2T",
330*2224f3a1Sclaudio 	    "DataPDUInOrder", "DataSequenceInOrder", "ErrorRecoveryLevel",
331*2224f3a1Sclaudio 	    NULL};
332*2224f3a1Sclaudio 	const char *opneg_always[] = {"HeaderDigest", "DataDigest", NULL};
333*2224f3a1Sclaudio 	const char *secneg[] = {"SessionType", "InitiatorName", "TargetName",
334*2224f3a1Sclaudio 	    "AuthMethod", NULL};
335*2224f3a1Sclaudio 	const char **p, **q;
3364125a3c4Sclaudio 
3374125a3c4Sclaudio 	switch (stage) {
3384125a3c4Sclaudio 	case ISCSI_LOGIN_STG_SECNEG:
3394125a3c4Sclaudio 		if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
340*2224f3a1Sclaudio 			len = sizeof(discovery) / sizeof(*discovery);
341*2224f3a1Sclaudio 			q = discovery;
3424125a3c4Sclaudio 		} else {
343*2224f3a1Sclaudio 			len = sizeof(secneg) / sizeof(*secneg);
344*2224f3a1Sclaudio 			q = secneg;
3454125a3c4Sclaudio 		}
346*2224f3a1Sclaudio 		if (!(kvp = calloc(len + 1, sizeof(*kvp))))
347*2224f3a1Sclaudio 			return NULL;
348*2224f3a1Sclaudio 		for (p = q; *p != NULL; i++, p++)
349*2224f3a1Sclaudio 			if (kvp_set_from_mine(&kvp[i], *p, c))
350*2224f3a1Sclaudio 				goto fail;
3514125a3c4Sclaudio 		break;
3524125a3c4Sclaudio 	case ISCSI_LOGIN_STG_OPNEG:
353*2224f3a1Sclaudio 		len = sizeof(opneg_always) / sizeof(*opneg_always);
354*2224f3a1Sclaudio 		if (conn_is_leading(c))
355*2224f3a1Sclaudio 			len += sizeof(leading_only) / sizeof(*leading_only);
356*2224f3a1Sclaudio 		if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength))
357*2224f3a1Sclaudio 			len++;
358*2224f3a1Sclaudio 		if (!(kvp = calloc(len + 1, sizeof(*kvp))))
3594125a3c4Sclaudio 			return NULL;
360*2224f3a1Sclaudio 		for (p = opneg_always; *p != NULL; i++, p++)
361*2224f3a1Sclaudio 			if (kvp_set_from_mine(&kvp[i], *p, c))
362*2224f3a1Sclaudio 				goto fail;
363*2224f3a1Sclaudio 		if (conn_is_leading(c))
364*2224f3a1Sclaudio 			for (p = leading_only; *p != NULL; i++, p++)
365*2224f3a1Sclaudio 				if (kvp_set_from_mine(&kvp[i], *p, c))
366*2224f3a1Sclaudio 					goto fail;
367*2224f3a1Sclaudio 		if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength) &&
368*2224f3a1Sclaudio 		    kvp_set_from_mine(&kvp[i], "MaxRecvDataSegmentLength", c))
369*2224f3a1Sclaudio 			goto fail;
3704125a3c4Sclaudio 		break;
3714125a3c4Sclaudio 	default:
3724125a3c4Sclaudio 		log_warnx("initiator_login_kvp: exit stage left");
3734125a3c4Sclaudio 		return NULL;
3744125a3c4Sclaudio 	}
3754125a3c4Sclaudio 	return kvp;
376*2224f3a1Sclaudio fail:
377*2224f3a1Sclaudio 	kvp_free(kvp);
378*2224f3a1Sclaudio 	return NULL;
3794125a3c4Sclaudio }
3804125a3c4Sclaudio 
381*2224f3a1Sclaudio #undef MINE_NOT_DEFAULT
3824125a3c4Sclaudio struct pdu *
3834125a3c4Sclaudio initiator_login_build(struct connection *c, struct task_login *tl)
3844125a3c4Sclaudio {
3854125a3c4Sclaudio 	struct pdu *p;
3864125a3c4Sclaudio 	struct kvp *kvp;
3874125a3c4Sclaudio 	struct iscsi_pdu_login_request *lreq;
3884125a3c4Sclaudio 	int n;
3894125a3c4Sclaudio 
3904125a3c4Sclaudio 	if (!(p = pdu_new()))
3914125a3c4Sclaudio 		return NULL;
3924125a3c4Sclaudio 	if (!(lreq = pdu_gethdr(p))) {
3934125a3c4Sclaudio 		pdu_free(p);
3944125a3c4Sclaudio 		return NULL;
3954125a3c4Sclaudio 	}
3964125a3c4Sclaudio 
3974125a3c4Sclaudio 	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
3984125a3c4Sclaudio 	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
3994125a3c4Sclaudio 		lreq->flags = ISCSI_LOGIN_F_T |
4004125a3c4Sclaudio 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) |
4014125a3c4Sclaudio 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
4024125a3c4Sclaudio 	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
4034125a3c4Sclaudio 		lreq->flags = ISCSI_LOGIN_F_T |
4044125a3c4Sclaudio 		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
4054125a3c4Sclaudio 		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
4064125a3c4Sclaudio 
4074125a3c4Sclaudio 	lreq->isid_base = htonl(tl->c->session->isid_base);
4084125a3c4Sclaudio 	lreq->isid_qual = htons(tl->c->session->isid_qual);
4094125a3c4Sclaudio 	lreq->tsih = tl->tsih;
4104125a3c4Sclaudio 	lreq->cid = htons(tl->c->cid);
4114125a3c4Sclaudio 	lreq->expstatsn = htonl(tl->c->expstatsn);
4124125a3c4Sclaudio 
4134125a3c4Sclaudio 	if (!(kvp = initiator_login_kvp(c, tl->stage))) {
4144125a3c4Sclaudio 		log_warn("initiator_login_kvp failed");
4154125a3c4Sclaudio 		return NULL;
4164125a3c4Sclaudio 	}
4174125a3c4Sclaudio 	if ((n = text_to_pdu(kvp, p)) == -1) {
418dda190faSclaudio 		kvp_free(kvp);
4194125a3c4Sclaudio 		return NULL;
4204125a3c4Sclaudio 	}
421dda190faSclaudio 	kvp_free(kvp);
4224125a3c4Sclaudio 
4234125a3c4Sclaudio 	if (n > 8192) {
4244125a3c4Sclaudio 		log_warn("initiator_login_build: help, I'm too verbose");
4254125a3c4Sclaudio 		pdu_free(p);
4264125a3c4Sclaudio 		return NULL;
4274125a3c4Sclaudio 	}
4284125a3c4Sclaudio 	n = htonl(n);
4294125a3c4Sclaudio 	/* copy 32bit value over ahslen and datalen */
430a86db512Sclaudio 	memcpy(&lreq->ahslen, &n, sizeof(n));
4314125a3c4Sclaudio 
4324125a3c4Sclaudio 	return p;
4334125a3c4Sclaudio }
4344125a3c4Sclaudio 
4354125a3c4Sclaudio struct pdu *
4364125a3c4Sclaudio initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
4374125a3c4Sclaudio {
4384125a3c4Sclaudio 	struct pdu *p;
4394125a3c4Sclaudio 	struct iscsi_pdu_text_request *lreq;
4404125a3c4Sclaudio 	int n;
4414125a3c4Sclaudio 
4424125a3c4Sclaudio 	if (!(p = pdu_new()))
4434125a3c4Sclaudio 		return NULL;
4444125a3c4Sclaudio 	if (!(lreq = pdu_gethdr(p)))
4454125a3c4Sclaudio 		return NULL;
4464125a3c4Sclaudio 
4474125a3c4Sclaudio 	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
4484125a3c4Sclaudio 	lreq->flags = ISCSI_TEXT_F_F;
4494125a3c4Sclaudio 	lreq->ttt = 0xffffffff;
4504125a3c4Sclaudio 
4514125a3c4Sclaudio 	if ((n = text_to_pdu(kvp, p)) == -1)
4524125a3c4Sclaudio 		return NULL;
4534125a3c4Sclaudio 	n = htonl(n);
454a86db512Sclaudio 	memcpy(&lreq->ahslen, &n, sizeof(n));
4554125a3c4Sclaudio 
4564125a3c4Sclaudio 	return p;
4574125a3c4Sclaudio }
4584125a3c4Sclaudio 
4594125a3c4Sclaudio void
4604125a3c4Sclaudio initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
4614125a3c4Sclaudio {
4624125a3c4Sclaudio 	struct task_login *tl = arg;
4634125a3c4Sclaudio 	struct iscsi_pdu_login_response *lresp;
4644125a3c4Sclaudio 	u_char *buf = NULL;
4654125a3c4Sclaudio 	struct kvp *kvp;
4664125a3c4Sclaudio 	size_t n, size;
4674125a3c4Sclaudio 
4684125a3c4Sclaudio 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
4694125a3c4Sclaudio 
4704125a3c4Sclaudio 	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
4714125a3c4Sclaudio 		log_warnx("Unexpected login response type %x",
4724125a3c4Sclaudio 		    ISCSI_PDU_OPCODE(lresp->opcode));
4734125a3c4Sclaudio 		conn_fail(c);
4744125a3c4Sclaudio 		goto done;
4754125a3c4Sclaudio 	}
4764125a3c4Sclaudio 
4774125a3c4Sclaudio 	if (lresp->flags & ISCSI_LOGIN_F_C) {
4784125a3c4Sclaudio 		log_warnx("Incomplete login responses are unsupported");
4794125a3c4Sclaudio 		conn_fail(c);
4804125a3c4Sclaudio 		goto done;
4814125a3c4Sclaudio 	}
4824125a3c4Sclaudio 
4834125a3c4Sclaudio 	size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
4844125a3c4Sclaudio 	    lresp->datalen[2];
4854125a3c4Sclaudio 	buf = pdu_getbuf(p, &n, PDU_DATA);
4864125a3c4Sclaudio 	if (size > n) {
4874125a3c4Sclaudio 		log_warnx("Bad login response");
4884125a3c4Sclaudio 		conn_fail(c);
4894125a3c4Sclaudio 		goto done;
4904125a3c4Sclaudio 	}
4914125a3c4Sclaudio 
4924125a3c4Sclaudio 	if (buf) {
4934125a3c4Sclaudio 		kvp = pdu_to_text(buf, size);
4944125a3c4Sclaudio 		if (kvp == NULL) {
4954125a3c4Sclaudio 			conn_fail(c);
4964125a3c4Sclaudio 			goto done;
4974125a3c4Sclaudio 		}
4984125a3c4Sclaudio 
4994125a3c4Sclaudio 		if (conn_parse_kvp(c, kvp) == -1) {
500dda190faSclaudio 			kvp_free(kvp);
5014125a3c4Sclaudio 			conn_fail(c);
5024125a3c4Sclaudio 			goto done;
5034125a3c4Sclaudio 		}
504dda190faSclaudio 		kvp_free(kvp);
5054125a3c4Sclaudio 	}
5064125a3c4Sclaudio 
5074125a3c4Sclaudio 	/* advance FSM if possible */
5084125a3c4Sclaudio 	if (lresp->flags & ISCSI_LOGIN_F_T)
5094125a3c4Sclaudio 		tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
5104125a3c4Sclaudio 
5114125a3c4Sclaudio 	switch (tl->stage) {
5124125a3c4Sclaudio 	case ISCSI_LOGIN_STG_SECNEG:
5134125a3c4Sclaudio 	case ISCSI_LOGIN_STG_OPNEG:
5144125a3c4Sclaudio 		/* free no longer used pdu */
5154125a3c4Sclaudio 		pdu_free(p);
5164125a3c4Sclaudio 		p = initiator_login_build(c, tl);
5174125a3c4Sclaudio 		if (p == NULL) {
5184125a3c4Sclaudio 			conn_fail(c);
5194125a3c4Sclaudio 			goto done;
5204125a3c4Sclaudio 		}
5214125a3c4Sclaudio 		break;
5224125a3c4Sclaudio 	case ISCSI_LOGIN_STG_FULL:
5234125a3c4Sclaudio 		conn_fsm(c, CONN_EV_LOGGED_IN);
52464d8b5c8Sclaudio 		conn_task_cleanup(c, &tl->task);
52564d8b5c8Sclaudio 		free(tl);
5264125a3c4Sclaudio 		goto done;
5274125a3c4Sclaudio 	default:
5284125a3c4Sclaudio 		log_warnx("initiator_login_cb: exit stage left");
5294125a3c4Sclaudio 		conn_fail(c);
5304125a3c4Sclaudio 		goto done;
5314125a3c4Sclaudio 	}
5324125a3c4Sclaudio 	conn_task_cleanup(c, &tl->task);
5334125a3c4Sclaudio 	/* add new pdu and re-issue the task */
5344125a3c4Sclaudio 	task_pdu_add(&tl->task, p);
5354125a3c4Sclaudio 	conn_task_issue(c, &tl->task);
5364125a3c4Sclaudio 	return;
5374125a3c4Sclaudio done:
5384125a3c4Sclaudio 	if (p)
5394125a3c4Sclaudio 		pdu_free(p);
5404125a3c4Sclaudio }
5414125a3c4Sclaudio 
5424125a3c4Sclaudio void
543cb408c6cSclaudio initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
544cb408c6cSclaudio {
545cb408c6cSclaudio 	struct task *t = arg;
546cb408c6cSclaudio 	struct iscsi_pdu_text_response *lresp;
547cb408c6cSclaudio 	u_char *buf = NULL;
548cb408c6cSclaudio 	struct kvp *kvp, *k;
549cb408c6cSclaudio 	size_t n, size;
550cb408c6cSclaudio 
551cb408c6cSclaudio 	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
552cb408c6cSclaudio 	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
553cb408c6cSclaudio 	case ISCSI_OP_TEXT_RESPONSE:
554cb408c6cSclaudio 		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
555cb408c6cSclaudio 		    lresp->datalen[2];
556cb408c6cSclaudio 		if (size == 0) {
557cb408c6cSclaudio 			/* empty response */
55812bd4d7fSclaudio 			session_shutdown(c->session);
559cb408c6cSclaudio 			break;
560cb408c6cSclaudio 		}
561cb408c6cSclaudio 		buf = pdu_getbuf(p, &n, PDU_DATA);
562cb408c6cSclaudio 		if (size > n || buf == NULL)
563cb408c6cSclaudio 			goto fail;
564cb408c6cSclaudio 		kvp = pdu_to_text(buf, size);
565cb408c6cSclaudio 		if (kvp == NULL)
566cb408c6cSclaudio 			goto fail;
567cb408c6cSclaudio 		log_debug("ISCSI_OP_TEXT_RESPONSE");
568cb408c6cSclaudio 		for (k = kvp; k->key; k++) {
569cb408c6cSclaudio 			log_debug("%s\t=>\t%s", k->key, k->value);
570cb408c6cSclaudio 		}
571dda190faSclaudio 		kvp_free(kvp);
57212bd4d7fSclaudio 		session_shutdown(c->session);
573cb408c6cSclaudio 		break;
574cb408c6cSclaudio 	default:
575cb408c6cSclaudio 		log_debug("initiator_discovery_cb: unexpected message type %x",
576cb408c6cSclaudio 		    ISCSI_PDU_OPCODE(lresp->opcode));
577cb408c6cSclaudio fail:
578cb408c6cSclaudio 		conn_fail(c);
57964d8b5c8Sclaudio 		pdu_free(p);
58064d8b5c8Sclaudio 		return;
581cb408c6cSclaudio 	}
5826727bd69Sclaudio 	conn_task_cleanup(c, t);
583cb408c6cSclaudio 	free(t);
584cb408c6cSclaudio 	pdu_free(p);
585cb408c6cSclaudio }
586cb408c6cSclaudio 
587cb408c6cSclaudio void
588cb408c6cSclaudio initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
589cb408c6cSclaudio {
590cb408c6cSclaudio 	struct task_logout *tl = arg;
591cb408c6cSclaudio 	struct iscsi_pdu_logout_response *loresp;
592cb408c6cSclaudio 
593cb408c6cSclaudio 	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
594cb408c6cSclaudio 	log_debug("initiator_logout_cb: "
59512bd4d7fSclaudio 	    "response %d, Time2Wait %d, Time2Retain %d",
596daed3c83Sclaudio 	    loresp->response, ntohs(loresp->time2wait),
597daed3c83Sclaudio 	    ntohs(loresp->time2retain));
598cb408c6cSclaudio 
599cb408c6cSclaudio 	switch (loresp->response) {
600cb408c6cSclaudio 	case ISCSI_LOGOUT_RESP_SUCCESS:
60112bd4d7fSclaudio 		if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
60212bd4d7fSclaudio 			conn_fsm(c, CONN_EV_LOGGED_OUT);
6030908bd10Sclaudio 			session_fsm(&c->session->sev, SESS_EV_CLOSED, 0);
60412bd4d7fSclaudio 		} else {
605cb408c6cSclaudio 			conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
6060908bd10Sclaudio 			session_fsm(&tl->c->sev, SESS_EV_CONN_CLOSED, 0);
60712bd4d7fSclaudio 		}
608cb408c6cSclaudio 		break;
609cb408c6cSclaudio 	case ISCSI_LOGOUT_RESP_UNKN_CID:
610cb408c6cSclaudio 		/* connection ID not found, retry will not help */
611cb408c6cSclaudio 		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
612cb408c6cSclaudio 		    tl->c->session->config.SessionName,
613cb408c6cSclaudio 		    tl->c->cid);
61412bd4d7fSclaudio 		conn_fsm(tl->c, CONN_EV_FREE);
615cb408c6cSclaudio 		break;
616cb408c6cSclaudio 	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
617cb408c6cSclaudio 	case ISCSI_LOGOUT_RESP_ERROR:
618cb408c6cSclaudio 	default:
619cb408c6cSclaudio 		/* need to retry logout after loresp->time2wait secs */
620cb408c6cSclaudio 		conn_fail(tl->c);
62164d8b5c8Sclaudio 		pdu_free(p);
62264d8b5c8Sclaudio 		return;
623cb408c6cSclaudio 	}
624cb408c6cSclaudio 
6256727bd69Sclaudio 	conn_task_cleanup(c, &tl->task);
626cb408c6cSclaudio 	free(tl);
627cb408c6cSclaudio 	pdu_free(p);
628cb408c6cSclaudio }
629cb408c6cSclaudio 
630bde1ae23Sclaudio char *
631bde1ae23Sclaudio default_initiator_name(void)
632bde1ae23Sclaudio {
633ea0867c0Sderaadt 	char *s, hostname[HOST_NAME_MAX+1];
634bde1ae23Sclaudio 
635bde1ae23Sclaudio 	if (gethostname(hostname, sizeof(hostname)))
636bde1ae23Sclaudio 		strlcpy(hostname, "initiator", sizeof(hostname));
637bde1ae23Sclaudio 	if ((s = strchr(hostname, '.')))
638bde1ae23Sclaudio 		*s = '\0';
639bde1ae23Sclaudio 	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
640bde1ae23Sclaudio 		return ISCSID_BASE_NAME ":initiator";
641bde1ae23Sclaudio 	return s;
642bde1ae23Sclaudio }
643