GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/iscsid/initiator.c Lines: 0 255 0.0 %
Date: 2017-11-07 Branches: 0 116 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: initiator.c,v 1.15 2015/01/16 15:57:06 deraadt Exp $ */
2
3
/*
4
 * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
#include <sys/queue.h>
21
#include <sys/socket.h>
22
#include <sys/uio.h>
23
24
#include <scsi/iscsi.h>
25
26
#include <event.h>
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
#include <unistd.h>
31
#include <limits.h>
32
33
#include "iscsid.h"
34
#include "log.h"
35
36
struct initiator *initiator;
37
38
struct task_login {
39
	struct task		 task;
40
	struct connection	*c;
41
	u_int16_t		 tsih;
42
	u_int8_t		 stage;
43
};
44
45
struct task_logout {
46
	struct task		 task;
47
	struct connection	*c;
48
	u_int8_t		 reason;
49
};
50
51
struct kvp	*initiator_login_kvp(struct connection *, u_int8_t);
52
struct pdu	*initiator_login_build(struct connection *,
53
		    struct task_login *);
54
struct pdu	*initiator_text_build(struct task *, struct session *,
55
		    struct kvp *);
56
57
void	initiator_login_cb(struct connection *, void *, struct pdu *);
58
void	initiator_discovery_cb(struct connection *, void *, struct pdu *);
59
void	initiator_logout_cb(struct connection *, void *, struct pdu *);
60
61
62
struct session_params		initiator_sess_defaults;
63
struct connection_params	initiator_conn_defaults;
64
65
struct initiator *
66
initiator_init(void)
67
{
68
	if (!(initiator = calloc(1, sizeof(*initiator))))
69
		fatal("initiator_init");
70
71
	initiator->config.isid_base =
72
	    arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
73
	initiator->config.isid_qual = arc4random_uniform(0xffff);
74
	TAILQ_INIT(&initiator->sessions);
75
76
	/* initialize initiator defaults */
77
	initiator_sess_defaults = iscsi_sess_defaults;
78
	initiator_conn_defaults = iscsi_conn_defaults;
79
	initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS;
80
	initiator_conn_defaults.MaxRecvDataSegmentLength = 65536;
81
82
	return initiator;
83
}
84
85
void
86
initiator_cleanup(struct initiator *i)
87
{
88
	struct session *s;
89
90
	while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
91
		TAILQ_REMOVE(&i->sessions, s, entry);
92
		session_cleanup(s);
93
	}
94
	free(initiator);
95
}
96
97
void
98
initiator_shutdown(struct initiator *i)
99
{
100
	struct session *s;
101
102
	log_debug("initiator_shutdown: going down");
103
104
	TAILQ_FOREACH(s, &initiator->sessions, entry)
105
		session_shutdown(s);
106
}
107
108
int
109
initiator_isdown(struct initiator *i)
110
{
111
	struct session *s;
112
	int inprogres = 0;
113
114
	TAILQ_FOREACH(s, &initiator->sessions, entry) {
115
		if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE))
116
			inprogres = 1;
117
	}
118
	return !inprogres;
119
}
120
121
struct session *
122
initiator_t2s(u_int target)
123
{
124
	struct session *s;
125
126
	TAILQ_FOREACH(s, &initiator->sessions, entry) {
127
		if (s->target == target)
128
			return s;
129
	}
130
	return NULL;
131
}
132
133
void
134
initiator_login(struct connection *c)
135
{
136
	struct task_login *tl;
137
	struct pdu *p;
138
139
	if (!(tl = calloc(1, sizeof(*tl)))) {
140
		log_warn("initiator_login");
141
		conn_fail(c);
142
		return;
143
	}
144
	tl->c = c;
145
	tl->stage = ISCSI_LOGIN_STG_SECNEG;
146
147
	if (!(p = initiator_login_build(c, tl))) {
148
		log_warn("initiator_login_build failed");
149
		free(tl);
150
		conn_fail(c);
151
		return;
152
	}
153
154
	task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL);
155
	task_pdu_add(&tl->task, p);
156
	conn_task_issue(c, &tl->task);
157
}
158
159
void
160
initiator_discovery(struct session *s)
161
{
162
	struct task *t;
163
	struct pdu *p;
164
	struct kvp kvp[] = {
165
		{ "SendTargets", "All" },
166
		{ NULL, NULL }
167
	};
168
169
	if (!(t = calloc(1, sizeof(*t)))) {
170
		log_warn("initiator_discovery");
171
		/* XXX sess_fail(c); */
172
		return;
173
	}
174
175
	if (!(p = initiator_text_build(t, s, kvp))) {
176
		log_warnx("initiator_text_build failed");
177
		free(t);
178
		/* XXX sess_fail(c); */
179
		return;
180
	}
181
182
	task_init(t, s, 0, t, initiator_discovery_cb, NULL);
183
	task_pdu_add(t, p);
184
	session_task_issue(s, t);
185
}
186
187
void
188
initiator_logout(struct session *s, struct connection *c, u_int8_t reason)
189
{
190
	struct task_logout *tl;
191
	struct pdu *p;
192
	struct iscsi_pdu_logout_request *loreq;
193
194
	if (!(tl = calloc(1, sizeof(*tl)))) {
195
		log_warn("initiator_logout");
196
		/* XXX sess_fail */
197
		return;
198
	}
199
	tl->c = c;
200
	tl->reason = reason;
201
202
	if (!(p = pdu_new())) {
203
		log_warn("initiator_logout");
204
		/* XXX sess_fail */
205
		free(tl);
206
		return;
207
	}
208
	if (!(loreq = pdu_gethdr(p))) {
209
		log_warn("initiator_logout");
210
		/* XXX sess_fail */
211
		pdu_free(p);
212
		free(tl);
213
		return;
214
	}
215
216
	loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
217
	loreq->flags = ISCSI_LOGOUT_F | reason;
218
	if (reason != ISCSI_LOGOUT_CLOSE_SESS)
219
		loreq->cid = c->cid;
220
221
	task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL);
222
	task_pdu_add(&tl->task, p);
223
	if (c && (c->state & CONN_RUNNING))
224
		conn_task_issue(c, &tl->task);
225
	else
226
		session_logout_issue(s, &tl->task);
227
}
228
229
void
230
initiator_nop_in_imm(struct connection *c, struct pdu *p)
231
{
232
	struct iscsi_pdu_nop_in *nopin;
233
	struct task *t;
234
235
	/* fixup NOP-IN to make it a NOP-OUT */
236
	nopin = pdu_getbuf(p, NULL, PDU_HEADER);
237
	nopin->maxcmdsn = 0;
238
	nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
239
240
	/* and schedule an immediate task */
241
	if (!(t = calloc(1, sizeof(*t)))) {
242
		log_warn("initiator_nop_in_imm");
243
		pdu_free(p);
244
		return;
245
	}
246
247
	task_init(t, c->session, 1, NULL, NULL, NULL);
248
	t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
249
	task_pdu_add(t, p);
250
	conn_task_issue(c, t);
251
}
252
253
struct kvp *
254
initiator_login_kvp(struct connection *c, u_int8_t stage)
255
{
256
	struct kvp *kvp;
257
	size_t nkvp;
258
259
	switch (stage) {
260
	case ISCSI_LOGIN_STG_SECNEG:
261
		if (!(kvp = calloc(4, sizeof(*kvp))))
262
			return NULL;
263
		kvp[0].key = "AuthMethod";
264
		kvp[0].value = "None";
265
		kvp[1].key = "InitiatorName";
266
		kvp[1].value = c->session->config.InitiatorName;
267
268
		if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
269
			kvp[2].key = "SessionType";
270
			kvp[2].value = "Discovery";
271
		} else {
272
			kvp[2].key = "TargetName";
273
			kvp[2].value = c->session->config.TargetName;
274
		}
275
		break;
276
	case ISCSI_LOGIN_STG_OPNEG:
277
		if (conn_gen_kvp(c, NULL, &nkvp) == -1)
278
			return NULL;
279
		nkvp += 1; /* add slot for terminator */
280
		if (!(kvp = calloc(nkvp, sizeof(*kvp))))
281
			return NULL;
282
		if (conn_gen_kvp(c, kvp, &nkvp) == -1) {
283
			free(kvp);
284
			return NULL;
285
		}
286
		break;
287
	default:
288
		log_warnx("initiator_login_kvp: exit stage left");
289
		return NULL;
290
	}
291
	return kvp;
292
}
293
294
struct pdu *
295
initiator_login_build(struct connection *c, struct task_login *tl)
296
{
297
	struct pdu *p;
298
	struct kvp *kvp;
299
	struct iscsi_pdu_login_request *lreq;
300
	int n;
301
302
	if (!(p = pdu_new()))
303
		return NULL;
304
	if (!(lreq = pdu_gethdr(p))) {
305
		pdu_free(p);
306
		return NULL;
307
	}
308
309
	lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
310
	if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
311
		lreq->flags = ISCSI_LOGIN_F_T |
312
		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) |
313
		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
314
	else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
315
		lreq->flags = ISCSI_LOGIN_F_T |
316
		    ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
317
		    ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
318
319
	lreq->isid_base = htonl(tl->c->session->isid_base);
320
	lreq->isid_qual = htons(tl->c->session->isid_qual);
321
	lreq->tsih = tl->tsih;
322
	lreq->cid = htons(tl->c->cid);
323
	lreq->expstatsn = htonl(tl->c->expstatsn);
324
325
	if (!(kvp = initiator_login_kvp(c, tl->stage))) {
326
		log_warn("initiator_login_kvp failed");
327
		return NULL;
328
	}
329
	if ((n = text_to_pdu(kvp, p)) == -1) {
330
		free(kvp);
331
		return NULL;
332
	}
333
	free(kvp);
334
335
	if (n > 8192) {
336
		log_warn("initiator_login_build: help, I'm too verbose");
337
		pdu_free(p);
338
		return NULL;
339
	}
340
	n = htonl(n);
341
	/* copy 32bit value over ahslen and datalen */
342
	memcpy(&lreq->ahslen, &n, sizeof(n));
343
344
	return p;
345
}
346
347
struct pdu *
348
initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
349
{
350
	struct pdu *p;
351
	struct iscsi_pdu_text_request *lreq;
352
	int n;
353
354
	if (!(p = pdu_new()))
355
		return NULL;
356
	if (!(lreq = pdu_gethdr(p)))
357
		return NULL;
358
359
	lreq->opcode = ISCSI_OP_TEXT_REQUEST;
360
	lreq->flags = ISCSI_TEXT_F_F;
361
	lreq->ttt = 0xffffffff;
362
363
	if ((n = text_to_pdu(kvp, p)) == -1)
364
		return NULL;
365
	n = htonl(n);
366
	memcpy(&lreq->ahslen, &n, sizeof(n));
367
368
	return p;
369
}
370
371
void
372
initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
373
{
374
	struct task_login *tl = arg;
375
	struct iscsi_pdu_login_response *lresp;
376
	u_char *buf = NULL;
377
	struct kvp *kvp;
378
	size_t n, size;
379
380
	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
381
382
	if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
383
		log_warnx("Unexpected login response type %x",
384
		    ISCSI_PDU_OPCODE(lresp->opcode));
385
		conn_fail(c);
386
		goto done;
387
	}
388
389
	if (lresp->flags & ISCSI_LOGIN_F_C) {
390
		log_warnx("Incomplete login responses are unsupported");
391
		conn_fail(c);
392
		goto done;
393
	}
394
395
	size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
396
	    lresp->datalen[2];
397
	buf = pdu_getbuf(p, &n, PDU_DATA);
398
	if (size > n) {
399
		log_warnx("Bad login response");
400
		conn_fail(c);
401
		goto done;
402
	}
403
404
	if (buf) {
405
		kvp = pdu_to_text(buf, size);
406
		if (kvp == NULL) {
407
			conn_fail(c);
408
			goto done;
409
		}
410
411
		if (conn_parse_kvp(c, kvp) == -1) {
412
			free(kvp);
413
			conn_fail(c);
414
			goto done;
415
		}
416
		free(kvp);
417
	}
418
419
	/* advance FSM if possible */
420
	if (lresp->flags & ISCSI_LOGIN_F_T)
421
		tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
422
423
	switch (tl->stage) {
424
	case ISCSI_LOGIN_STG_SECNEG:
425
	case ISCSI_LOGIN_STG_OPNEG:
426
		/* free no longer used pdu */
427
		pdu_free(p);
428
		p = initiator_login_build(c, tl);
429
		if (p == NULL) {
430
			conn_fail(c);
431
			goto done;
432
		}
433
		break;
434
	case ISCSI_LOGIN_STG_FULL:
435
		conn_fsm(c, CONN_EV_LOGGED_IN);
436
		conn_task_cleanup(c, &tl->task);
437
		free(tl);
438
		goto done;
439
	default:
440
		log_warnx("initiator_login_cb: exit stage left");
441
		conn_fail(c);
442
		goto done;
443
	}
444
	conn_task_cleanup(c, &tl->task);
445
	/* add new pdu and re-issue the task */
446
	task_pdu_add(&tl->task, p);
447
	conn_task_issue(c, &tl->task);
448
	return;
449
done:
450
	if (p)
451
		pdu_free(p);
452
}
453
454
void
455
initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
456
{
457
	struct task *t = arg;
458
	struct iscsi_pdu_text_response *lresp;
459
	u_char *buf = NULL;
460
	struct kvp *kvp, *k;
461
	size_t n, size;
462
463
	lresp = pdu_getbuf(p, NULL, PDU_HEADER);
464
	switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
465
	case ISCSI_OP_TEXT_RESPONSE:
466
		size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
467
		    lresp->datalen[2];
468
		if (size == 0) {
469
			/* empty response */
470
			session_shutdown(c->session);
471
			break;
472
		}
473
		buf = pdu_getbuf(p, &n, PDU_DATA);
474
		if (size > n || buf == NULL)
475
			goto fail;
476
		kvp = pdu_to_text(buf, size);
477
		if (kvp == NULL)
478
			goto fail;
479
		log_debug("ISCSI_OP_TEXT_RESPONSE");
480
		for (k = kvp; k->key; k++) {
481
			log_debug("%s\t=>\t%s", k->key, k->value);
482
		}
483
		free(kvp);
484
		session_shutdown(c->session);
485
		break;
486
	default:
487
		log_debug("initiator_discovery_cb: unexpected message type %x",
488
		    ISCSI_PDU_OPCODE(lresp->opcode));
489
fail:
490
		conn_fail(c);
491
		pdu_free(p);
492
		return;
493
	}
494
	conn_task_cleanup(c, t);
495
	free(t);
496
	pdu_free(p);
497
}
498
499
void
500
initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
501
{
502
	struct task_logout *tl = arg;
503
	struct iscsi_pdu_logout_response *loresp;
504
505
	loresp = pdu_getbuf(p, NULL, PDU_HEADER);
506
	log_debug("initiator_logout_cb: "
507
	    "response %d, Time2Wait %d, Time2Retain %d",
508
	    loresp->response, ntohs(loresp->time2wait),
509
	    ntohs(loresp->time2retain));
510
511
	switch (loresp->response) {
512
	case ISCSI_LOGOUT_RESP_SUCCESS:
513
		if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
514
			conn_fsm(c, CONN_EV_LOGGED_OUT);
515
			session_fsm(c->session, SESS_EV_CLOSED, NULL, 0);
516
		} else {
517
			conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
518
			session_fsm(c->session, SESS_EV_CONN_CLOSED, tl->c, 0);
519
		}
520
		break;
521
	case ISCSI_LOGOUT_RESP_UNKN_CID:
522
		/* connection ID not found, retry will not help */
523
		log_warnx("%s: logout failed, cid %d unknown, giving up\n",
524
		    tl->c->session->config.SessionName,
525
		    tl->c->cid);
526
		conn_fsm(tl->c, CONN_EV_FREE);
527
		break;
528
	case ISCSI_LOGOUT_RESP_NO_SUPPORT:
529
	case ISCSI_LOGOUT_RESP_ERROR:
530
	default:
531
		/* need to retry logout after loresp->time2wait secs */
532
		conn_fail(tl->c);
533
		pdu_free(p);
534
		return;
535
	}
536
537
	conn_task_cleanup(c, &tl->task);
538
	free(tl);
539
	pdu_free(p);
540
}
541
542
char *
543
default_initiator_name(void)
544
{
545
	char *s, hostname[HOST_NAME_MAX+1];
546
547
	if (gethostname(hostname, sizeof(hostname)))
548
		strlcpy(hostname, "initiator", sizeof(hostname));
549
	if ((s = strchr(hostname, '.')))
550
		*s = '\0';
551
	if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
552
		return ISCSID_BASE_NAME ":initiator";
553
	return s;
554
}