1255570Strasz/*-
2255570Strasz * Copyright (c) 2012 The FreeBSD Foundation
3255570Strasz * All rights reserved.
4255570Strasz *
5255570Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6255570Strasz * from the FreeBSD Foundation.
7255570Strasz *
8255570Strasz * Redistribution and use in source and binary forms, with or without
9255570Strasz * modification, are permitted provided that the following conditions
10255570Strasz * are met:
11255570Strasz * 1. Redistributions of source code must retain the above copyright
12255570Strasz *    notice, this list of conditions and the following disclaimer.
13255570Strasz * 2. Redistributions in binary form must reproduce the above copyright
14255570Strasz *    notice, this list of conditions and the following disclaimer in the
15255570Strasz *    documentation and/or other materials provided with the distribution.
16255570Strasz *
17255570Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18255570Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19255570Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20255570Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21255570Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22255570Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23255570Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24255570Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25255570Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26255570Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27255570Strasz * SUCH DAMAGE.
28255570Strasz *
29255570Strasz */
30255570Strasz
31270888Strasz#include <sys/cdefs.h>
32270888Strasz__FBSDID("$FreeBSD: releng/10.2/usr.sbin/iscsid/login.c 276613 2015-01-03 13:08:08Z mav $");
33270888Strasz
34255570Strasz#include <sys/types.h>
35269065Smav#include <sys/ioctl.h>
36255570Strasz#include <assert.h>
37255570Strasz#include <stdbool.h>
38255570Strasz#include <stdio.h>
39255570Strasz#include <stdlib.h>
40255570Strasz#include <string.h>
41255570Strasz#include <netinet/in.h>
42255570Strasz
43255570Strasz#include "iscsid.h"
44255570Strasz#include "iscsi_proto.h"
45255570Strasz
46255570Straszstatic int
47255570Straszlogin_nsg(const struct pdu *response)
48255570Strasz{
49255570Strasz	struct iscsi_bhs_login_response *bhslr;
50255570Strasz
51255570Strasz	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
52255570Strasz
53255570Strasz	return (bhslr->bhslr_flags & 0x03);
54255570Strasz}
55255570Strasz
56255570Straszstatic void
57255570Straszlogin_set_nsg(struct pdu *request, int nsg)
58255570Strasz{
59255570Strasz	struct iscsi_bhs_login_request *bhslr;
60255570Strasz
61255570Strasz	assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
62255570Strasz	    nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
63255570Strasz	    nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
64255570Strasz
65255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
66255570Strasz
67255570Strasz	bhslr->bhslr_flags &= 0xFC;
68255570Strasz	bhslr->bhslr_flags |= nsg;
69255570Strasz}
70255570Strasz
71255570Straszstatic void
72255570Straszlogin_set_csg(struct pdu *request, int csg)
73255570Strasz{
74255570Strasz	struct iscsi_bhs_login_request *bhslr;
75255570Strasz
76255570Strasz	assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
77255570Strasz	    csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
78255570Strasz	    csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
79255570Strasz
80255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
81255570Strasz
82255570Strasz	bhslr->bhslr_flags &= 0xF3;
83255570Strasz	bhslr->bhslr_flags |= csg << 2;
84255570Strasz}
85255570Strasz
86255570Straszstatic const char *
87255570Straszlogin_target_error_str(int class, int detail)
88255570Strasz{
89255570Strasz	static char msg[128];
90255570Strasz
91255570Strasz	/*
92255570Strasz	 * RFC 3270, 10.13.5.  Status-Class and Status-Detail
93255570Strasz	 */
94255570Strasz	switch (class) {
95255570Strasz	case 0x01:
96255570Strasz		switch (detail) {
97255570Strasz		case 0x01:
98255570Strasz			return ("Target moved temporarily");
99255570Strasz		case 0x02:
100255570Strasz			return ("Target moved permanently");
101255570Strasz		default:
102255570Strasz			snprintf(msg, sizeof(msg), "unknown redirection; "
103255570Strasz			    "Status-Class 0x%x, Status-Detail 0x%x",
104255570Strasz			    class, detail);
105255570Strasz			return (msg);
106255570Strasz		}
107255570Strasz	case 0x02:
108255570Strasz		switch (detail) {
109255570Strasz		case 0x00:
110255570Strasz			return ("Initiator error");
111255570Strasz		case 0x01:
112255570Strasz			return ("Authentication failure");
113255570Strasz		case 0x02:
114255570Strasz			return ("Authorization failure");
115255570Strasz		case 0x03:
116255570Strasz			return ("Not found");
117255570Strasz		case 0x04:
118255570Strasz			return ("Target removed");
119255570Strasz		case 0x05:
120255570Strasz			return ("Unsupported version");
121255570Strasz		case 0x06:
122255570Strasz			return ("Too many connections");
123255570Strasz		case 0x07:
124255570Strasz			return ("Missing parameter");
125255570Strasz		case 0x08:
126255570Strasz			return ("Can't include in session");
127255570Strasz		case 0x09:
128255570Strasz			return ("Session type not supported");
129255570Strasz		case 0x0a:
130255570Strasz			return ("Session does not exist");
131255570Strasz		case 0x0b:
132255570Strasz			return ("Invalid during login");
133255570Strasz		default:
134255570Strasz			snprintf(msg, sizeof(msg), "unknown initiator error; "
135255570Strasz			    "Status-Class 0x%x, Status-Detail 0x%x",
136255570Strasz			    class, detail);
137255570Strasz			return (msg);
138255570Strasz		}
139255570Strasz	case 0x03:
140255570Strasz		switch (detail) {
141255570Strasz		case 0x00:
142255570Strasz			return ("Target error");
143255570Strasz		case 0x01:
144255570Strasz			return ("Service unavailable");
145255570Strasz		case 0x02:
146255570Strasz			return ("Out of resources");
147255570Strasz		default:
148255570Strasz			snprintf(msg, sizeof(msg), "unknown target error; "
149255570Strasz			    "Status-Class 0x%x, Status-Detail 0x%x",
150255570Strasz			    class, detail);
151255570Strasz			return (msg);
152255570Strasz		}
153255570Strasz	default:
154255570Strasz		snprintf(msg, sizeof(msg), "unknown error; "
155255570Strasz		    "Status-Class 0x%x, Status-Detail 0x%x",
156255570Strasz		    class, detail);
157255570Strasz		return (msg);
158255570Strasz	}
159255570Strasz}
160255570Strasz
161269065Smavstatic void
162269065Smavkernel_modify(const struct connection *conn, const char *target_address)
163269065Smav{
164269065Smav	struct iscsi_session_modify ism;
165269065Smav	int error;
166269065Smav
167269065Smav	memset(&ism, 0, sizeof(ism));
168269065Smav	ism.ism_session_id = conn->conn_session_id;
169269065Smav	memcpy(&ism.ism_conf, &conn->conn_conf, sizeof(ism.ism_conf));
170269065Smav	strlcpy(ism.ism_conf.isc_target_addr, target_address,
171269065Smav	    sizeof(ism.ism_conf.isc_target));
172269065Smav	error = ioctl(conn->conn_iscsi_fd, ISCSISMODIFY, &ism);
173269065Smav	if (error != 0) {
174269065Smav		log_err(1, "failed to redirect to %s: ISCSISMODIFY",
175269065Smav		    target_address);
176269065Smav	}
177269065Smav}
178269065Smav
179269065Smav/*
180269065Smav * XXX:	The way it works is suboptimal; what should happen is described
181269065Smav *	in draft-gilligan-iscsi-fault-tolerance-00.  That, however, would
182269065Smav *	be much more complicated: we would need to keep "dependencies"
183269065Smav *	for sessions, so that, in case described in draft and using draft
184269065Smav *	terminology, we would have three sessions: one for discovery,
185274870Strasz *	one for initial target portal, and one for redirect portal.
186269065Smav *	This would allow us to "backtrack" on connection failure,
187269065Smav *	as described in draft.
188269065Smav */
189269065Smavstatic void
190269065Smavlogin_handle_redirection(struct connection *conn, struct pdu *response)
191269065Smav{
192269065Smav	struct iscsi_bhs_login_response *bhslr;
193269065Smav	struct keys *response_keys;
194269065Smav	const char *target_address;
195269065Smav
196269065Smav	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
197269065Smav	assert (bhslr->bhslr_status_class == 1);
198269065Smav
199269065Smav	response_keys = keys_new();
200269065Smav	keys_load(response_keys, response);
201269065Smav
202269065Smav	target_address = keys_find(response_keys, "TargetAddress");
203269065Smav	if (target_address == NULL)
204269065Smav		log_errx(1, "received redirection without TargetAddress");
205269065Smav	if (target_address[0] == '\0')
206269065Smav		log_errx(1, "received redirection with empty TargetAddress");
207269065Smav	if (strlen(target_address) >=
208269065Smav	    sizeof(conn->conn_conf.isc_target_addr) - 1)
209269065Smav		log_errx(1, "received TargetAddress is too long");
210269065Smav
211269065Smav	log_debugx("received redirection to \"%s\"", target_address);
212269065Smav	kernel_modify(conn, target_address);
213275259Strasz	keys_delete(response_keys);
214269065Smav}
215269065Smav
216255570Straszstatic struct pdu *
217269069Smavlogin_receive(struct connection *conn)
218255570Strasz{
219255570Strasz	struct pdu *response;
220255570Strasz	struct iscsi_bhs_login_response *bhslr;
221255570Strasz	const char *errorstr;
222269069Smav	static bool initial = true;
223255570Strasz
224255570Strasz	response = pdu_new(conn);
225255570Strasz	pdu_receive(response);
226255570Strasz	if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) {
227255570Strasz		log_errx(1, "protocol error: received invalid opcode 0x%x",
228255570Strasz		    response->pdu_bhs->bhs_opcode);
229255570Strasz	}
230255570Strasz	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
231255570Strasz	/*
232255570Strasz	 * XXX: Implement the C flag some day.
233255570Strasz	 */
234255570Strasz	if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0)
235255570Strasz		log_errx(1, "received Login PDU with unsupported \"C\" flag");
236255570Strasz	if (bhslr->bhslr_version_max != 0x00)
237255570Strasz		log_errx(1, "received Login PDU with unsupported "
238255570Strasz		    "Version-max 0x%x", bhslr->bhslr_version_max);
239255570Strasz	if (bhslr->bhslr_version_active != 0x00)
240255570Strasz		log_errx(1, "received Login PDU with unsupported "
241255570Strasz		    "Version-active 0x%x", bhslr->bhslr_version_active);
242269065Smav	if (bhslr->bhslr_status_class == 1) {
243269065Smav		login_handle_redirection(conn, response);
244269065Smav		log_debugx("redirection handled; exiting");
245269065Smav		exit(0);
246269065Smav	}
247255570Strasz	if (bhslr->bhslr_status_class != 0) {
248255570Strasz		errorstr = login_target_error_str(bhslr->bhslr_status_class,
249255570Strasz		    bhslr->bhslr_status_detail);
250255570Strasz		fail(conn, errorstr);
251255570Strasz		log_errx(1, "target returned error: %s", errorstr);
252255570Strasz	}
253255570Strasz	if (initial == false &&
254255570Strasz	    ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
255255570Strasz		/*
256255570Strasz		 * It's a warning, not an error, to work around what seems
257255570Strasz		 * to be bug in NetBSD iSCSI target.
258255570Strasz		 */
259255570Strasz		log_warnx("received Login PDU with wrong StatSN: "
260276613Smav		    "is %u, should be %u", ntohl(bhslr->bhslr_statsn),
261255570Strasz		    conn->conn_statsn + 1);
262255570Strasz	}
263268703Smav	conn->conn_tsih = ntohs(bhslr->bhslr_tsih);
264255570Strasz	conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
265255570Strasz
266269069Smav	initial = false;
267269069Smav
268255570Strasz	return (response);
269255570Strasz}
270255570Strasz
271255570Straszstatic struct pdu *
272269068Smavlogin_new_request(struct connection *conn, int csg)
273255570Strasz{
274255570Strasz	struct pdu *request;
275255570Strasz	struct iscsi_bhs_login_request *bhslr;
276269068Smav	int nsg;
277255570Strasz
278255570Strasz	request = pdu_new(conn);
279255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
280255570Strasz	bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST |
281255570Strasz	    ISCSI_BHS_OPCODE_IMMEDIATE;
282269068Smav
283255570Strasz	bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT;
284269068Smav	switch (csg) {
285269068Smav	case BHSLR_STAGE_SECURITY_NEGOTIATION:
286269068Smav		nsg = BHSLR_STAGE_OPERATIONAL_NEGOTIATION;
287269068Smav		break;
288269068Smav	case BHSLR_STAGE_OPERATIONAL_NEGOTIATION:
289269068Smav		nsg = BHSLR_STAGE_FULL_FEATURE_PHASE;
290269068Smav		break;
291269068Smav	default:
292269068Smav		assert(!"invalid csg");
293269068Smav		log_errx(1, "invalid csg %d", csg);
294269068Smav	}
295269068Smav	login_set_csg(request, csg);
296269068Smav	login_set_nsg(request, nsg);
297269068Smav
298255570Strasz	memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid));
299268703Smav	bhslr->bhslr_tsih = htons(conn->conn_tsih);
300255570Strasz	bhslr->bhslr_initiator_task_tag = 0;
301255570Strasz	bhslr->bhslr_cmdsn = 0;
302255570Strasz	bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
303255570Strasz
304255570Strasz	return (request);
305255570Strasz}
306255570Strasz
307255570Straszstatic int
308255570Straszlogin_list_prefers(const char *list,
309255570Strasz    const char *choice1, const char *choice2)
310255570Strasz{
311255570Strasz	char *tofree, *str, *token;
312255570Strasz
313255570Strasz	tofree = str = checked_strdup(list);
314255570Strasz
315255570Strasz	while ((token = strsep(&str, ",")) != NULL) {
316255570Strasz		if (strcmp(token, choice1) == 0) {
317255570Strasz			free(tofree);
318255570Strasz			return (1);
319255570Strasz		}
320255570Strasz		if (strcmp(token, choice2) == 0) {
321255570Strasz			free(tofree);
322255570Strasz			return (2);
323255570Strasz		}
324255570Strasz	}
325255570Strasz	free(tofree);
326255570Strasz	return (-1);
327255570Strasz}
328255570Strasz
329255570Straszstatic void
330255570Straszlogin_negotiate_key(struct connection *conn, const char *name,
331255570Strasz    const char *value)
332255570Strasz{
333255570Strasz	int which, tmp;
334255570Strasz
335255570Strasz	if (strcmp(name, "TargetAlias") == 0) {
336255570Strasz		strlcpy(conn->conn_target_alias, value,
337255570Strasz		    sizeof(conn->conn_target_alias));
338255570Strasz	} else if (strcmp(value, "Irrelevant") == 0) {
339255570Strasz		/* Ignore. */
340255570Strasz	} else if (strcmp(name, "HeaderDigest") == 0) {
341255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
342255570Strasz		switch (which) {
343255570Strasz		case 1:
344255570Strasz			log_debugx("target prefers CRC32C "
345255570Strasz			    "for header digest; we'll use it");
346255570Strasz			conn->conn_header_digest = CONN_DIGEST_CRC32C;
347255570Strasz			break;
348255570Strasz		case 2:
349255570Strasz			log_debugx("target prefers not to do "
350255570Strasz			    "header digest; we'll comply");
351255570Strasz			break;
352255570Strasz		default:
353255570Strasz			log_warnx("target sent unrecognized "
354255570Strasz			    "HeaderDigest value \"%s\"; will use None", value);
355255570Strasz			break;
356255570Strasz		}
357255570Strasz	} else if (strcmp(name, "DataDigest") == 0) {
358255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
359255570Strasz		switch (which) {
360255570Strasz		case 1:
361255570Strasz			log_debugx("target prefers CRC32C "
362255570Strasz			    "for data digest; we'll use it");
363255570Strasz			conn->conn_data_digest = CONN_DIGEST_CRC32C;
364255570Strasz			break;
365255570Strasz		case 2:
366255570Strasz			log_debugx("target prefers not to do "
367255570Strasz			    "data digest; we'll comply");
368255570Strasz			break;
369255570Strasz		default:
370255570Strasz			log_warnx("target sent unrecognized "
371255570Strasz			    "DataDigest value \"%s\"; will use None", value);
372255570Strasz			break;
373255570Strasz		}
374255570Strasz	} else if (strcmp(name, "MaxConnections") == 0) {
375255570Strasz		/* Ignore. */
376255570Strasz	} else if (strcmp(name, "InitialR2T") == 0) {
377255570Strasz		if (strcmp(value, "Yes") == 0)
378255570Strasz			conn->conn_initial_r2t = true;
379255570Strasz		else
380255570Strasz			conn->conn_initial_r2t = false;
381255570Strasz	} else if (strcmp(name, "ImmediateData") == 0) {
382255570Strasz		if (strcmp(value, "Yes") == 0)
383255570Strasz			conn->conn_immediate_data = true;
384255570Strasz		else
385255570Strasz			conn->conn_immediate_data = false;
386255570Strasz	} else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
387255570Strasz		tmp = strtoul(value, NULL, 10);
388255570Strasz		if (tmp <= 0)
389255570Strasz			log_errx(1, "received invalid "
390255570Strasz			    "MaxRecvDataSegmentLength");
391276234Smav		if (tmp > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
392276234Smav			log_debugx("capping MaxRecvDataSegmentLength "
393276234Smav			    "from %d to %d", tmp, ISCSI_MAX_DATA_SEGMENT_LENGTH);
394276234Smav			tmp = ISCSI_MAX_DATA_SEGMENT_LENGTH;
395276234Smav		}
396255570Strasz		conn->conn_max_data_segment_length = tmp;
397255570Strasz	} else if (strcmp(name, "MaxBurstLength") == 0) {
398255570Strasz		if (conn->conn_immediate_data) {
399255570Strasz			tmp = strtoul(value, NULL, 10);
400255570Strasz			if (tmp <= 0)
401255570Strasz				log_errx(1, "received invalid MaxBurstLength");
402255570Strasz			conn->conn_max_burst_length = tmp;
403255570Strasz		}
404255570Strasz	} else if (strcmp(name, "FirstBurstLength") == 0) {
405255570Strasz		tmp = strtoul(value, NULL, 10);
406255570Strasz		if (tmp <= 0)
407255570Strasz			log_errx(1, "received invalid FirstBurstLength");
408255570Strasz		conn->conn_first_burst_length = tmp;
409255570Strasz	} else if (strcmp(name, "DefaultTime2Wait") == 0) {
410255570Strasz		/* Ignore */
411255570Strasz	} else if (strcmp(name, "DefaultTime2Retain") == 0) {
412255570Strasz		/* Ignore */
413255570Strasz	} else if (strcmp(name, "MaxOutstandingR2T") == 0) {
414255570Strasz		/* Ignore */
415255570Strasz	} else if (strcmp(name, "DataPDUInOrder") == 0) {
416255570Strasz		/* Ignore */
417255570Strasz	} else if (strcmp(name, "DataSequenceInOrder") == 0) {
418255570Strasz		/* Ignore */
419255570Strasz	} else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
420255570Strasz		/* Ignore */
421255570Strasz	} else if (strcmp(name, "OFMarker") == 0) {
422255570Strasz		/* Ignore */
423255570Strasz	} else if (strcmp(name, "IFMarker") == 0) {
424255570Strasz		/* Ignore */
425255570Strasz	} else if (strcmp(name, "TargetPortalGroupTag") == 0) {
426255570Strasz		/* Ignore */
427255570Strasz	} else {
428255570Strasz		log_debugx("unknown key \"%s\"; ignoring",  name);
429255570Strasz	}
430255570Strasz}
431255570Strasz
432255570Straszstatic void
433255570Straszlogin_negotiate(struct connection *conn)
434255570Strasz{
435255570Strasz	struct pdu *request, *response;
436255570Strasz	struct keys *request_keys, *response_keys;
437255570Strasz	struct iscsi_bhs_login_response *bhslr;
438271706Strasz	int i, nrequests = 0;
439255570Strasz
440269067Smav	log_debugx("beginning operational parameter negotiation");
441269068Smav	request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
442255570Strasz	request_keys = keys_new();
443255678Strasz
444255678Strasz	/*
445255678Strasz	 * The following keys are irrelevant for discovery sessions.
446255678Strasz	 */
447255570Strasz	if (conn->conn_conf.isc_discovery == 0) {
448255570Strasz		if (conn->conn_conf.isc_header_digest != 0)
449255570Strasz			keys_add(request_keys, "HeaderDigest", "CRC32C");
450255678Strasz		else
451255678Strasz			keys_add(request_keys, "HeaderDigest", "None");
452255570Strasz		if (conn->conn_conf.isc_data_digest != 0)
453255570Strasz			keys_add(request_keys, "DataDigest", "CRC32C");
454255678Strasz		else
455255678Strasz			keys_add(request_keys, "DataDigest", "None");
456255570Strasz
457255570Strasz		keys_add(request_keys, "ImmediateData", "Yes");
458255570Strasz		keys_add_int(request_keys, "MaxBurstLength",
459276234Smav		    2 * ISCSI_MAX_DATA_SEGMENT_LENGTH);
460255570Strasz		keys_add_int(request_keys, "FirstBurstLength",
461255570Strasz		    ISCSI_MAX_DATA_SEGMENT_LENGTH);
462255678Strasz		keys_add(request_keys, "InitialR2T", "Yes");
463276234Smav		keys_add(request_keys, "MaxOutstandingR2T", "1");
464255678Strasz	} else {
465255678Strasz		keys_add(request_keys, "HeaderDigest", "None");
466255678Strasz		keys_add(request_keys, "DataDigest", "None");
467255570Strasz	}
468255678Strasz
469255570Strasz	keys_add_int(request_keys, "MaxRecvDataSegmentLength",
470255570Strasz	    ISCSI_MAX_DATA_SEGMENT_LENGTH);
471255570Strasz	keys_add(request_keys, "DefaultTime2Wait", "0");
472255570Strasz	keys_add(request_keys, "DefaultTime2Retain", "0");
473255678Strasz	keys_add(request_keys, "ErrorRecoveryLevel", "0");
474255570Strasz	keys_save(request_keys, request);
475255570Strasz	keys_delete(request_keys);
476255570Strasz	request_keys = NULL;
477255570Strasz	pdu_send(request);
478255570Strasz	pdu_delete(request);
479255570Strasz	request = NULL;
480255570Strasz
481269069Smav	response = login_receive(conn);
482255570Strasz	response_keys = keys_new();
483255570Strasz	keys_load(response_keys, response);
484255570Strasz	for (i = 0; i < KEYS_MAX; i++) {
485255570Strasz		if (response_keys->keys_names[i] == NULL)
486255570Strasz			break;
487255570Strasz
488255570Strasz		login_negotiate_key(conn,
489255570Strasz		    response_keys->keys_names[i], response_keys->keys_values[i]);
490255570Strasz	}
491255570Strasz
492271706Strasz	keys_delete(response_keys);
493271706Strasz	response_keys = NULL;
494271706Strasz
495271706Strasz	for (;;) {
496271706Strasz		bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
497271706Strasz		if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0)
498271706Strasz			break;
499271706Strasz
500271706Strasz		nrequests++;
501271706Strasz		if (nrequests > 5) {
502271706Strasz			log_warnx("received login response "
503271706Strasz			    "without the \"T\" flag too many times; giving up");
504271706Strasz			break;
505271706Strasz		}
506271706Strasz
507271706Strasz		log_debugx("received login response "
508271706Strasz		    "without the \"T\" flag; sending another request");
509271706Strasz
510271706Strasz		pdu_delete(response);
511271706Strasz
512271706Strasz		request = login_new_request(conn,
513271706Strasz		    BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
514271706Strasz		pdu_send(request);
515271706Strasz		pdu_delete(request);
516271706Strasz
517271706Strasz		response = login_receive(conn);
518271706Strasz	}
519271706Strasz
520271706Strasz	if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE)
521255570Strasz		log_warnx("received final login response with wrong NSG 0x%x",
522255570Strasz		    login_nsg(response));
523271706Strasz	pdu_delete(response);
524255570Strasz
525269067Smav	log_debugx("operational parameter negotiation done; "
526255570Strasz	    "transitioning to Full Feature phase");
527255570Strasz}
528255570Strasz
529255570Straszstatic void
530255570Straszlogin_send_chap_a(struct connection *conn)
531255570Strasz{
532255570Strasz	struct pdu *request;
533255570Strasz	struct keys *request_keys;
534255570Strasz
535269068Smav	request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
536255570Strasz	request_keys = keys_new();
537255570Strasz	keys_add(request_keys, "CHAP_A", "5");
538255570Strasz	keys_save(request_keys, request);
539255570Strasz	keys_delete(request_keys);
540255570Strasz	pdu_send(request);
541255570Strasz	pdu_delete(request);
542255570Strasz}
543255570Strasz
544255570Straszstatic void
545255570Straszlogin_send_chap_r(struct pdu *response)
546255570Strasz{
547255570Strasz	struct connection *conn;
548255570Strasz	struct pdu *request;
549255570Strasz	struct keys *request_keys, *response_keys;
550274866Strasz	struct rchap *rchap;
551255570Strasz	const char *chap_a, *chap_c, *chap_i;
552274866Strasz	char *chap_r;
553274866Strasz	int error;
554274866Strasz        char *mutual_chap_c, *mutual_chap_i;
555255570Strasz
556255570Strasz	/*
557255570Strasz	 * As in the rest of the initiator, 'request' means
558255570Strasz	 * 'initiator -> target', and 'response' means 'target -> initiator',
559255570Strasz	 *
560255570Strasz	 * So, here the 'response' from the target is the packet that contains
561255570Strasz	 * CHAP challenge; our CHAP response goes into 'request'.
562255570Strasz	 */
563255570Strasz
564255570Strasz	conn = response->pdu_connection;
565255570Strasz
566255570Strasz	response_keys = keys_new();
567255570Strasz	keys_load(response_keys, response);
568255570Strasz
569255570Strasz	/*
570255570Strasz	 * First, compute the response.
571255570Strasz	 */
572255570Strasz	chap_a = keys_find(response_keys, "CHAP_A");
573255570Strasz	if (chap_a == NULL)
574255570Strasz		log_errx(1, "received CHAP packet without CHAP_A");
575255570Strasz	chap_c = keys_find(response_keys, "CHAP_C");
576255570Strasz	if (chap_c == NULL)
577255570Strasz		log_errx(1, "received CHAP packet without CHAP_C");
578255570Strasz	chap_i = keys_find(response_keys, "CHAP_I");
579255570Strasz	if (chap_i == NULL)
580255570Strasz		log_errx(1, "received CHAP packet without CHAP_I");
581255570Strasz
582274866Strasz	if (strcmp(chap_a, "5") != 0) {
583255570Strasz		log_errx(1, "received CHAP packet "
584255570Strasz		    "with unsupported CHAP_A \"%s\"", chap_a);
585274866Strasz	}
586255570Strasz
587274866Strasz	rchap = rchap_new(conn->conn_conf.isc_secret);
588274866Strasz	error = rchap_receive(rchap, chap_i, chap_c);
589274866Strasz	if (error != 0) {
590274866Strasz		log_errx(1, "received CHAP packet "
591274866Strasz		    "with malformed CHAP_I or CHAP_C");
592274866Strasz	}
593274866Strasz	chap_r = rchap_get_response(rchap);
594274866Strasz	rchap_delete(rchap);
595274866Strasz
596255570Strasz	keys_delete(response_keys);
597255570Strasz
598269068Smav	request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
599255570Strasz	request_keys = keys_new();
600255570Strasz	keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user);
601255570Strasz	keys_add(request_keys, "CHAP_R", chap_r);
602255570Strasz	free(chap_r);
603255570Strasz
604255570Strasz	/*
605255570Strasz	 * If we want mutual authentication, we're expected to send
606255570Strasz	 * our CHAP_I/CHAP_C now.
607255570Strasz	 */
608255570Strasz	if (conn->conn_conf.isc_mutual_user[0] != '\0') {
609255570Strasz		log_debugx("requesting mutual authentication; "
610255570Strasz		    "binary challenge size is %zd bytes",
611274866Strasz		    sizeof(conn->conn_mutual_chap->chap_challenge));
612255570Strasz
613274866Strasz		assert(conn->conn_mutual_chap == NULL);
614274866Strasz		conn->conn_mutual_chap = chap_new();
615274866Strasz		mutual_chap_i = chap_get_id(conn->conn_mutual_chap);
616274866Strasz		mutual_chap_c = chap_get_challenge(conn->conn_mutual_chap);
617255570Strasz		keys_add(request_keys, "CHAP_I", mutual_chap_i);
618255570Strasz		keys_add(request_keys, "CHAP_C", mutual_chap_c);
619274866Strasz		free(mutual_chap_i);
620255570Strasz		free(mutual_chap_c);
621255570Strasz	}
622255570Strasz
623255570Strasz	keys_save(request_keys, request);
624255570Strasz	keys_delete(request_keys);
625255570Strasz	pdu_send(request);
626255570Strasz	pdu_delete(request);
627255570Strasz}
628255570Strasz
629255570Straszstatic void
630255570Straszlogin_verify_mutual(const struct pdu *response)
631255570Strasz{
632255570Strasz	struct connection *conn;
633255570Strasz	struct keys *response_keys;
634255570Strasz	const char *chap_n, *chap_r;
635255570Strasz	int error;
636255570Strasz
637255570Strasz	conn = response->pdu_connection;
638255570Strasz
639255570Strasz	response_keys = keys_new();
640255570Strasz	keys_load(response_keys, response);
641255570Strasz
642255570Strasz        chap_n = keys_find(response_keys, "CHAP_N");
643255570Strasz        if (chap_n == NULL)
644255570Strasz                log_errx(1, "received CHAP Response PDU without CHAP_N");
645255570Strasz        chap_r = keys_find(response_keys, "CHAP_R");
646255570Strasz        if (chap_r == NULL)
647255570Strasz                log_errx(1, "received CHAP Response PDU without CHAP_R");
648255570Strasz
649274866Strasz	error = chap_receive(conn->conn_mutual_chap, chap_r);
650274866Strasz	if (error != 0)
651274866Strasz                log_errx(1, "received CHAP Response PDU with invalid CHAP_R");
652274866Strasz
653255570Strasz	if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) {
654255570Strasz		fail(conn, "Mutual CHAP failed");
655255570Strasz		log_errx(1, "mutual CHAP authentication failed: wrong user");
656255570Strasz	}
657255570Strasz
658274866Strasz	error = chap_authenticate(conn->conn_mutual_chap,
659274866Strasz	    conn->conn_conf.isc_mutual_secret);
660274866Strasz	if (error != 0) {
661255570Strasz		fail(conn, "Mutual CHAP failed");
662255570Strasz                log_errx(1, "mutual CHAP authentication failed: wrong secret");
663255570Strasz	}
664255570Strasz
665274866Strasz	keys_delete(response_keys);
666274866Strasz	chap_delete(conn->conn_mutual_chap);
667274866Strasz	conn->conn_mutual_chap = NULL;
668255570Strasz
669255570Strasz	log_debugx("mutual CHAP authentication succeeded");
670255570Strasz}
671255570Strasz
672255570Straszstatic void
673255570Straszlogin_chap(struct connection *conn)
674255570Strasz{
675255570Strasz	struct pdu *response;
676255570Strasz
677255570Strasz	log_debugx("beginning CHAP authentication; sending CHAP_A");
678255570Strasz	login_send_chap_a(conn);
679255570Strasz
680255570Strasz	log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I");
681269069Smav	response = login_receive(conn);
682255570Strasz
683255570Strasz	log_debugx("sending CHAP_N/CHAP_R");
684255570Strasz	login_send_chap_r(response);
685255570Strasz	pdu_delete(response);
686255570Strasz
687255570Strasz	/*
688255570Strasz	 * XXX: Make sure this is not susceptible to MITM.
689255570Strasz	 */
690255570Strasz
691255570Strasz	log_debugx("waiting for CHAP result");
692269069Smav	response = login_receive(conn);
693255570Strasz	if (conn->conn_conf.isc_mutual_user[0] != '\0')
694255570Strasz		login_verify_mutual(response);
695255570Strasz	pdu_delete(response);
696255570Strasz
697255570Strasz	log_debugx("CHAP authentication done");
698255570Strasz}
699255570Strasz
700255570Straszvoid
701255570Straszlogin(struct connection *conn)
702255570Strasz{
703255570Strasz	struct pdu *request, *response;
704255570Strasz	struct keys *request_keys, *response_keys;
705255570Strasz	struct iscsi_bhs_login_response *bhslr2;
706255570Strasz	const char *auth_method;
707255570Strasz	int i;
708255570Strasz
709255570Strasz	log_debugx("beginning Login phase; sending Login PDU");
710269068Smav	request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
711255570Strasz	request_keys = keys_new();
712255678Strasz	if (conn->conn_conf.isc_mutual_user[0] != '\0') {
713255678Strasz		keys_add(request_keys, "AuthMethod", "CHAP");
714255678Strasz	} else if (conn->conn_conf.isc_user[0] != '\0') {
715255678Strasz		/*
716255678Strasz		 * Give target a chance to skip authentication if it
717255678Strasz		 * doesn't feel like it.
718255678Strasz		 *
719255678Strasz		 * None is first, CHAP second; this is to work around
720255678Strasz		 * what seems to be LIO (Linux target) bug: otherwise,
721255678Strasz		 * if target is configured with no authentication,
722255678Strasz		 * and we are configured to authenticate, the target
723255678Strasz		 * will erroneously respond with AuthMethod=CHAP
724255678Strasz		 * instead of AuthMethod=None, and will subsequently
725255678Strasz		 * fail the connection.  This usually happens with
726255678Strasz		 * Discovery sessions, which default to no authentication.
727255678Strasz		 */
728255678Strasz		keys_add(request_keys, "AuthMethod", "None,CHAP");
729255678Strasz	} else {
730255570Strasz		keys_add(request_keys, "AuthMethod", "None");
731255678Strasz	}
732255570Strasz	keys_add(request_keys, "InitiatorName",
733255570Strasz	    conn->conn_conf.isc_initiator);
734255570Strasz	if (conn->conn_conf.isc_initiator_alias[0] != '\0') {
735255570Strasz		keys_add(request_keys, "InitiatorAlias",
736255570Strasz		    conn->conn_conf.isc_initiator_alias);
737255570Strasz	}
738255570Strasz	if (conn->conn_conf.isc_discovery == 0) {
739255570Strasz		keys_add(request_keys, "SessionType", "Normal");
740255570Strasz		keys_add(request_keys,
741255570Strasz		    "TargetName", conn->conn_conf.isc_target);
742255570Strasz	} else {
743255570Strasz		keys_add(request_keys, "SessionType", "Discovery");
744255570Strasz	}
745255570Strasz	keys_save(request_keys, request);
746255570Strasz	keys_delete(request_keys);
747255570Strasz	pdu_send(request);
748255570Strasz	pdu_delete(request);
749255570Strasz
750269069Smav	response = login_receive(conn);
751255570Strasz
752255570Strasz	response_keys = keys_new();
753255570Strasz	keys_load(response_keys, response);
754255570Strasz
755255570Strasz	for (i = 0; i < KEYS_MAX; i++) {
756255570Strasz		if (response_keys->keys_names[i] == NULL)
757255570Strasz			break;
758255570Strasz
759255570Strasz		/*
760255570Strasz		 * Not interested in AuthMethod at this point; we only need
761255570Strasz		 * to parse things such as TargetAlias.
762255570Strasz		 *
763255570Strasz		 * XXX: This is somewhat ugly.  We should have a way to apply
764274870Strasz		 *      all the keys to the session and use that by default
765274870Strasz		 *      instead of discarding them.
766255570Strasz		 */
767255570Strasz		if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0)
768255570Strasz			continue;
769255570Strasz
770255570Strasz		login_negotiate_key(conn,
771255570Strasz		    response_keys->keys_names[i], response_keys->keys_values[i]);
772255570Strasz	}
773255570Strasz
774255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
775255570Strasz	if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 &&
776255570Strasz	    login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
777255678Strasz		if (conn->conn_conf.isc_mutual_user[0] != '\0') {
778255678Strasz			log_errx(1, "target requested transition "
779269067Smav			    "to operational parameter negotiation, "
780269067Smav			    "but we require mutual CHAP");
781255678Strasz		}
782255678Strasz
783255570Strasz		log_debugx("target requested transition "
784269067Smav		    "to operational parameter negotiation");
785255570Strasz		keys_delete(response_keys);
786255570Strasz		pdu_delete(response);
787255570Strasz		login_negotiate(conn);
788255570Strasz		return;
789255570Strasz	}
790255570Strasz
791255570Strasz	auth_method = keys_find(response_keys, "AuthMethod");
792255570Strasz	if (auth_method == NULL)
793255570Strasz		log_errx(1, "received response without AuthMethod");
794255570Strasz	if (strcmp(auth_method, "None") == 0) {
795255678Strasz		if (conn->conn_conf.isc_mutual_user[0] != '\0') {
796255678Strasz			log_errx(1, "target does not require authantication, "
797255678Strasz			    "but we require mutual CHAP");
798255678Strasz		}
799255678Strasz
800255570Strasz		log_debugx("target does not require authentication");
801255570Strasz		keys_delete(response_keys);
802255570Strasz		pdu_delete(response);
803255570Strasz		login_negotiate(conn);
804255570Strasz		return;
805255570Strasz	}
806255570Strasz
807255570Strasz	if (strcmp(auth_method, "CHAP") != 0) {
808255570Strasz		fail(conn, "Unsupported AuthMethod");
809255570Strasz		log_errx(1, "received response "
810255570Strasz		    "with unsupported AuthMethod \"%s\"", auth_method);
811255570Strasz	}
812255570Strasz
813255570Strasz	if (conn->conn_conf.isc_user[0] == '\0' ||
814255570Strasz	    conn->conn_conf.isc_secret[0] == '\0') {
815255570Strasz		fail(conn, "Authentication required");
816255570Strasz		log_errx(1, "target requests CHAP authentication, but we don't "
817255570Strasz		    "have user and secret");
818255570Strasz	}
819255570Strasz
820255570Strasz	keys_delete(response_keys);
821255570Strasz	response_keys = NULL;
822255570Strasz	pdu_delete(response);
823255570Strasz	response = NULL;
824255570Strasz
825255570Strasz	login_chap(conn);
826255570Strasz	login_negotiate(conn);
827255570Strasz}
828