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 *
initiator_init(void)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
initiator_cleanup(struct initiator * i)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
initiator_shutdown(struct initiator * i)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
initiator_isdown(struct initiator * i)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 *
initiator_t2s(u_int target)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
initiator_login(struct connection * c)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
initiator_discovery(struct session * s)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
initiator_logout(struct session * s,struct connection * c,u_int8_t reason)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
initiator_nop_in_imm(struct connection * c,struct pdu * p)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 *
initiator_login_kvp(struct connection * c,u_int8_t stage)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 *
initiator_login_build(struct connection * c,struct task_login * tl)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 *
initiator_text_build(struct task * t,struct session * s,struct kvp * kvp)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
initiator_login_cb(struct connection * c,void * arg,struct pdu * p)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
initiator_discovery_cb(struct connection * c,void * arg,struct pdu * p)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
initiator_logout_cb(struct connection * c,void * arg,struct pdu * p)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 *
default_initiator_name(void)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 }
555