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 * $FreeBSD$
30255570Strasz */
31255570Strasz
32255570Strasz#include <assert.h>
33255570Strasz#include <stdbool.h>
34255570Strasz#include <stdint.h>
35255570Strasz#include <stdio.h>
36255570Strasz#include <stdlib.h>
37255570Strasz#include <string.h>
38255570Strasz#include <unistd.h>
39255570Strasz#include <netinet/in.h>
40255570Strasz#include <openssl/err.h>
41255570Strasz#include <openssl/md5.h>
42255570Strasz#include <openssl/rand.h>
43255570Strasz
44255570Strasz#include "ctld.h"
45255570Strasz#include "iscsi_proto.h"
46255570Strasz
47255570Straszstatic void login_send_error(struct pdu *request,
48255570Strasz    char class, char detail);
49255570Strasz
50255570Straszstatic void
51255570Straszlogin_set_nsg(struct pdu *response, int nsg)
52255570Strasz{
53255570Strasz	struct iscsi_bhs_login_response *bhslr;
54255570Strasz
55255570Strasz	assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
56255570Strasz	    nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
57255570Strasz	    nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
58255570Strasz
59255570Strasz	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
60255570Strasz
61255570Strasz	bhslr->bhslr_flags &= 0xFC;
62255570Strasz	bhslr->bhslr_flags |= nsg;
63255570Strasz}
64255570Strasz
65255570Straszstatic int
66255570Straszlogin_csg(const struct pdu *request)
67255570Strasz{
68255570Strasz	struct iscsi_bhs_login_request *bhslr;
69255570Strasz
70255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
71255570Strasz
72255570Strasz	return ((bhslr->bhslr_flags & 0x0C) >> 2);
73255570Strasz}
74255570Strasz
75255570Straszstatic void
76255570Straszlogin_set_csg(struct pdu *response, int csg)
77255570Strasz{
78255570Strasz	struct iscsi_bhs_login_response *bhslr;
79255570Strasz
80255570Strasz	assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
81255570Strasz	    csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
82255570Strasz	    csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
83255570Strasz
84255570Strasz	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
85255570Strasz
86255570Strasz	bhslr->bhslr_flags &= 0xF3;
87255570Strasz	bhslr->bhslr_flags |= csg << 2;
88255570Strasz}
89255570Strasz
90255570Straszstatic struct pdu *
91255570Straszlogin_receive(struct connection *conn, bool initial)
92255570Strasz{
93255570Strasz	struct pdu *request;
94255570Strasz	struct iscsi_bhs_login_request *bhslr;
95255570Strasz
96255570Strasz	request = pdu_new(conn);
97255570Strasz	pdu_receive(request);
98255570Strasz	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
99255570Strasz	    ISCSI_BHS_OPCODE_LOGIN_REQUEST) {
100255570Strasz		/*
101255570Strasz		 * The first PDU in session is special - if we receive any PDU
102255570Strasz		 * different than login request, we have to drop the connection
103255570Strasz		 * without sending response ("A target receiving any PDU
104255570Strasz		 * except a Login request before the Login Phase is started MUST
105255570Strasz		 * immediately terminate the connection on which the PDU
106255570Strasz		 * was received.")
107255570Strasz		 */
108255570Strasz		if (initial == false)
109255570Strasz			login_send_error(request, 0x02, 0x0b);
110255570Strasz		log_errx(1, "protocol error: received invalid opcode 0x%x",
111255570Strasz		    request->pdu_bhs->bhs_opcode);
112255570Strasz	}
113255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
114255570Strasz	/*
115255570Strasz	 * XXX: Implement the C flag some day.
116255570Strasz	 */
117255570Strasz	if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) {
118255570Strasz		login_send_error(request, 0x03, 0x00);
119255570Strasz		log_errx(1, "received Login PDU with unsupported \"C\" flag");
120255570Strasz	}
121255570Strasz	if (bhslr->bhslr_version_max != 0x00) {
122255570Strasz		login_send_error(request, 0x02, 0x05);
123255570Strasz		log_errx(1, "received Login PDU with unsupported "
124255570Strasz		    "Version-max 0x%x", bhslr->bhslr_version_max);
125255570Strasz	}
126255570Strasz	if (bhslr->bhslr_version_min != 0x00) {
127255570Strasz		login_send_error(request, 0x02, 0x05);
128255570Strasz		log_errx(1, "received Login PDU with unsupported "
129255570Strasz		    "Version-min 0x%x", bhslr->bhslr_version_min);
130255570Strasz	}
131255570Strasz	if (request->pdu_data_len == 0) {
132255570Strasz		login_send_error(request, 0x02, 0x00);
133255570Strasz		log_errx(1, "received Login PDU with empty data segment");
134255570Strasz	}
135255570Strasz	if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) {
136255570Strasz		login_send_error(request, 0x02, 0x05);
137255570Strasz		log_errx(1, "received Login PDU with decreasing CmdSN: "
138255570Strasz		    "was %d, is %d", conn->conn_cmdsn,
139255570Strasz		    ntohl(bhslr->bhslr_cmdsn));
140255570Strasz	}
141255570Strasz	if (initial == false &&
142255570Strasz	    ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
143255570Strasz		login_send_error(request, 0x02, 0x05);
144255570Strasz		log_errx(1, "received Login PDU with wrong ExpStatSN: "
145255570Strasz		    "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn),
146255570Strasz		    conn->conn_statsn);
147255570Strasz	}
148255570Strasz	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
149255570Strasz
150255570Strasz	return (request);
151255570Strasz}
152255570Strasz
153255570Straszstatic struct pdu *
154255570Straszlogin_new_response(struct pdu *request)
155255570Strasz{
156255570Strasz	struct pdu *response;
157255570Strasz	struct connection *conn;
158255570Strasz	struct iscsi_bhs_login_request *bhslr;
159255570Strasz	struct iscsi_bhs_login_response *bhslr2;
160255570Strasz
161255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
162255570Strasz	conn = request->pdu_connection;
163255570Strasz
164255570Strasz	response = pdu_new_response(request);
165255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
166255570Strasz	bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE;
167255570Strasz	login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION);
168255570Strasz	memcpy(bhslr2->bhslr_isid,
169255570Strasz	    bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid));
170255570Strasz	bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
171255570Strasz	bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
172255570Strasz	bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
173255570Strasz	bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
174255570Strasz
175255570Strasz	return (response);
176255570Strasz}
177255570Strasz
178255570Straszstatic void
179255570Straszlogin_send_error(struct pdu *request, char class, char detail)
180255570Strasz{
181255570Strasz	struct pdu *response;
182255570Strasz	struct iscsi_bhs_login_response *bhslr2;
183255570Strasz
184255570Strasz	log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; "
185255570Strasz	    "see next line for reason", class, detail);
186255570Strasz	response = login_new_response(request);
187255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
188255570Strasz	bhslr2->bhslr_status_class = class;
189255570Strasz	bhslr2->bhslr_status_detail = detail;
190255570Strasz
191255570Strasz	pdu_send(response);
192255570Strasz	pdu_delete(response);
193255570Strasz}
194255570Strasz
195255570Straszstatic int
196255570Straszlogin_list_contains(const char *list, const char *what)
197255570Strasz{
198255570Strasz	char *tofree, *str, *token;
199255570Strasz
200255570Strasz	tofree = str = checked_strdup(list);
201255570Strasz
202255570Strasz	while ((token = strsep(&str, ",")) != NULL) {
203255570Strasz		if (strcmp(token, what) == 0) {
204255570Strasz			free(tofree);
205255570Strasz			return (1);
206255570Strasz		}
207255570Strasz	}
208255570Strasz	free(tofree);
209255570Strasz	return (0);
210255570Strasz}
211255570Strasz
212255570Straszstatic int
213255570Straszlogin_list_prefers(const char *list,
214255570Strasz    const char *choice1, const char *choice2)
215255570Strasz{
216255570Strasz	char *tofree, *str, *token;
217255570Strasz
218255570Strasz	tofree = str = checked_strdup(list);
219255570Strasz
220255570Strasz	while ((token = strsep(&str, ",")) != NULL) {
221255570Strasz		if (strcmp(token, choice1) == 0) {
222255570Strasz			free(tofree);
223255570Strasz			return (1);
224255570Strasz		}
225255570Strasz		if (strcmp(token, choice2) == 0) {
226255570Strasz			free(tofree);
227255570Strasz			return (2);
228255570Strasz		}
229255570Strasz	}
230255570Strasz	free(tofree);
231255570Strasz	return (-1);
232255570Strasz}
233255570Strasz
234255570Straszstatic int
235255570Straszlogin_hex2int(const char hex)
236255570Strasz{
237255570Strasz	switch (hex) {
238255570Strasz	case '0':
239255570Strasz		return (0x00);
240255570Strasz	case '1':
241255570Strasz		return (0x01);
242255570Strasz	case '2':
243255570Strasz		return (0x02);
244255570Strasz	case '3':
245255570Strasz		return (0x03);
246255570Strasz	case '4':
247255570Strasz		return (0x04);
248255570Strasz	case '5':
249255570Strasz		return (0x05);
250255570Strasz	case '6':
251255570Strasz		return (0x06);
252255570Strasz	case '7':
253255570Strasz		return (0x07);
254255570Strasz	case '8':
255255570Strasz		return (0x08);
256255570Strasz	case '9':
257255570Strasz		return (0x09);
258255570Strasz	case 'a':
259255570Strasz	case 'A':
260255570Strasz		return (0x0a);
261255570Strasz	case 'b':
262255570Strasz	case 'B':
263255570Strasz		return (0x0b);
264255570Strasz	case 'c':
265255570Strasz	case 'C':
266255570Strasz		return (0x0c);
267255570Strasz	case 'd':
268255570Strasz	case 'D':
269255570Strasz		return (0x0d);
270255570Strasz	case 'e':
271255570Strasz	case 'E':
272255570Strasz		return (0x0e);
273255570Strasz	case 'f':
274255570Strasz	case 'F':
275255570Strasz		return (0x0f);
276255570Strasz	default:
277255570Strasz		return (-1);
278255570Strasz	}
279255570Strasz}
280255570Strasz
281255570Strasz/*
282255570Strasz * XXX: Review this _carefully_.
283255570Strasz */
284255570Straszstatic int
285255570Straszlogin_hex2bin(const char *hex, char **binp, size_t *bin_lenp)
286255570Strasz{
287255570Strasz	int i, hex_len, nibble;
288255570Strasz	bool lo = true; /* As opposed to 'hi'. */
289255570Strasz	char *bin;
290255570Strasz	size_t bin_off, bin_len;
291255570Strasz
292255570Strasz	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
293255570Strasz		log_warnx("malformed variable, should start with \"0x\"");
294255570Strasz		return (-1);
295255570Strasz	}
296255570Strasz
297255570Strasz	hex += strlen("0x");
298255570Strasz	hex_len = strlen(hex);
299255570Strasz	if (hex_len < 1) {
300255570Strasz		log_warnx("malformed variable; doesn't contain anything "
301255570Strasz		    "but \"0x\"");
302255570Strasz		return (-1);
303255570Strasz	}
304255570Strasz
305255570Strasz	bin_len = hex_len / 2 + hex_len % 2;
306255570Strasz	bin = calloc(bin_len, 1);
307255570Strasz	if (bin == NULL)
308255570Strasz		log_err(1, "calloc");
309255570Strasz
310255570Strasz	bin_off = bin_len - 1;
311255570Strasz	for (i = hex_len - 1; i >= 0; i--) {
312255570Strasz		nibble = login_hex2int(hex[i]);
313255570Strasz		if (nibble < 0) {
314255570Strasz			log_warnx("malformed variable, invalid char \"%c\"",
315255570Strasz			    hex[i]);
316255570Strasz			return (-1);
317255570Strasz		}
318255570Strasz
319255570Strasz		assert(bin_off < bin_len);
320255570Strasz		if (lo) {
321255570Strasz			bin[bin_off] = nibble;
322255570Strasz			lo = false;
323255570Strasz		} else {
324255570Strasz			bin[bin_off] |= nibble << 4;
325255570Strasz			bin_off--;
326255570Strasz			lo = true;
327255570Strasz		}
328255570Strasz	}
329255570Strasz
330255570Strasz	*binp = bin;
331255570Strasz	*bin_lenp = bin_len;
332255570Strasz	return (0);
333255570Strasz}
334255570Strasz
335255570Straszstatic char *
336255570Straszlogin_bin2hex(const char *bin, size_t bin_len)
337255570Strasz{
338255570Strasz	unsigned char *hex, *tmp, ch;
339255570Strasz	size_t hex_len;
340255570Strasz	size_t i;
341255570Strasz
342255570Strasz	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
343255570Strasz	hex = malloc(hex_len);
344255570Strasz	if (hex == NULL)
345255570Strasz		log_err(1, "malloc");
346255570Strasz
347255570Strasz	tmp = hex;
348255570Strasz	tmp += sprintf(tmp, "0x");
349255570Strasz	for (i = 0; i < bin_len; i++) {
350255570Strasz		ch = bin[i];
351255570Strasz		tmp += sprintf(tmp, "%02x", ch);
352255570Strasz	}
353255570Strasz
354255570Strasz	return (hex);
355255570Strasz}
356255570Strasz
357255570Straszstatic void
358255570Straszlogin_compute_md5(const char id, const char *secret,
359255570Strasz    const void *challenge, size_t challenge_len, void *response,
360255570Strasz    size_t response_len)
361255570Strasz{
362255570Strasz	MD5_CTX ctx;
363255570Strasz	int rv;
364255570Strasz
365255570Strasz	assert(response_len == MD5_DIGEST_LENGTH);
366255570Strasz
367255570Strasz	MD5_Init(&ctx);
368255570Strasz	MD5_Update(&ctx, &id, sizeof(id));
369255570Strasz	MD5_Update(&ctx, secret, strlen(secret));
370255570Strasz	MD5_Update(&ctx, challenge, challenge_len);
371255570Strasz	rv = MD5_Final(response, &ctx);
372255570Strasz	if (rv != 1)
373255570Strasz		log_errx(1, "MD5_Final");
374255570Strasz}
375255570Strasz
376255570Strasz#define	LOGIN_CHALLENGE_LEN	1024
377255570Strasz
378255570Straszstatic struct pdu *
379255570Straszlogin_receive_chap_a(struct connection *conn)
380255570Strasz{
381255570Strasz	struct pdu *request;
382255570Strasz	struct keys *request_keys;
383255570Strasz	const char *chap_a;
384255570Strasz
385255570Strasz	request = login_receive(conn, false);
386255570Strasz	request_keys = keys_new();
387255570Strasz	keys_load(request_keys, request);
388255570Strasz
389255570Strasz	chap_a = keys_find(request_keys, "CHAP_A");
390255570Strasz	if (chap_a == NULL) {
391255570Strasz		login_send_error(request, 0x02, 0x07);
392255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_A");
393255570Strasz	}
394255570Strasz	if (login_list_contains(chap_a, "5") == 0) {
395255570Strasz		login_send_error(request, 0x02, 0x01);
396255570Strasz		log_errx(1, "received CHAP Login PDU with unsupported CHAP_A "
397255570Strasz		    "\"%s\"", chap_a);
398255570Strasz	}
399255570Strasz	keys_delete(request_keys);
400255570Strasz
401255570Strasz	return (request);
402255570Strasz}
403255570Strasz
404255570Straszstatic void
405255570Straszlogin_send_chap_c(struct pdu *request, const unsigned char id,
406255570Strasz    const void *challenge, const size_t challenge_len)
407255570Strasz{
408255570Strasz	struct pdu *response;
409255570Strasz	struct keys *response_keys;
410255570Strasz	char *chap_c, chap_i[4];
411255570Strasz
412255570Strasz	chap_c = login_bin2hex(challenge, challenge_len);
413255570Strasz	snprintf(chap_i, sizeof(chap_i), "%d", id);
414255570Strasz
415255570Strasz	response = login_new_response(request);
416255570Strasz	response_keys = keys_new();
417255570Strasz	keys_add(response_keys, "CHAP_A", "5");
418255570Strasz	keys_add(response_keys, "CHAP_I", chap_i);
419255570Strasz	keys_add(response_keys, "CHAP_C", chap_c);
420255570Strasz	free(chap_c);
421255570Strasz	keys_save(response_keys, response);
422256192Strasz	pdu_send(response);
423256192Strasz	pdu_delete(response);
424255570Strasz	keys_delete(response_keys);
425255570Strasz}
426255570Strasz
427255570Straszstatic struct pdu *
428255570Straszlogin_receive_chap_r(struct connection *conn,
429255570Strasz    struct auth_group *ag, const unsigned char id, const void *challenge,
430255570Strasz    const size_t challenge_len, const struct auth **cap)
431255570Strasz{
432255570Strasz	struct pdu *request;
433255570Strasz	struct keys *request_keys;
434255570Strasz	const char *chap_n, *chap_r;
435255570Strasz	char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH];
436255570Strasz	size_t response_bin_len;
437255570Strasz	const struct auth *auth;
438255570Strasz	int error;
439255570Strasz
440255570Strasz	request = login_receive(conn, false);
441255570Strasz	request_keys = keys_new();
442255570Strasz	keys_load(request_keys, request);
443255570Strasz
444255570Strasz	chap_n = keys_find(request_keys, "CHAP_N");
445255570Strasz	if (chap_n == NULL) {
446255570Strasz		login_send_error(request, 0x02, 0x07);
447255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_N");
448255570Strasz	}
449255570Strasz	chap_r = keys_find(request_keys, "CHAP_R");
450255570Strasz	if (chap_r == NULL) {
451255570Strasz		login_send_error(request, 0x02, 0x07);
452255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_R");
453255570Strasz	}
454255570Strasz	error = login_hex2bin(chap_r, &response_bin, &response_bin_len);
455255570Strasz	if (error != 0) {
456255570Strasz		login_send_error(request, 0x02, 0x07);
457255570Strasz		log_errx(1, "received CHAP Login PDU with malformed CHAP_R");
458255570Strasz	}
459255570Strasz
460255570Strasz	/*
461255570Strasz	 * Verify the response.
462255570Strasz	 */
463255570Strasz	assert(ag->ag_type == AG_TYPE_CHAP ||
464255570Strasz	    ag->ag_type == AG_TYPE_CHAP_MUTUAL);
465255570Strasz	auth = auth_find(ag, chap_n);
466255570Strasz	if (auth == NULL) {
467255570Strasz		login_send_error(request, 0x02, 0x01);
468255570Strasz		log_errx(1, "received CHAP Login with invalid user \"%s\"",
469255570Strasz		    chap_n);
470255570Strasz	}
471255570Strasz
472255570Strasz	assert(auth->a_secret != NULL);
473255570Strasz	assert(strlen(auth->a_secret) > 0);
474255570Strasz	login_compute_md5(id, auth->a_secret, challenge,
475255570Strasz	    challenge_len, expected_response_bin,
476255570Strasz	    sizeof(expected_response_bin));
477255570Strasz
478255570Strasz	if (memcmp(response_bin, expected_response_bin,
479255570Strasz	    sizeof(expected_response_bin)) != 0) {
480255570Strasz		login_send_error(request, 0x02, 0x01);
481255570Strasz		log_errx(1, "CHAP authentication failed for user \"%s\"",
482255570Strasz		    auth->a_user);
483255570Strasz	}
484255570Strasz
485255570Strasz	keys_delete(request_keys);
486255570Strasz	free(response_bin);
487255570Strasz
488255570Strasz	*cap = auth;
489255570Strasz	return (request);
490255570Strasz}
491255570Strasz
492255570Straszstatic void
493255570Straszlogin_send_chap_success(struct pdu *request,
494255570Strasz    const struct auth *auth)
495255570Strasz{
496255570Strasz	struct pdu *response;
497255570Strasz	struct keys *request_keys, *response_keys;
498255570Strasz	struct iscsi_bhs_login_response *bhslr2;
499255570Strasz	const char *chap_i, *chap_c;
500255570Strasz	char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH];
501255570Strasz	size_t challenge_len;
502255570Strasz	unsigned char id;
503255570Strasz	int error;
504255570Strasz
505255570Strasz	response = login_new_response(request);
506255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
507255570Strasz	bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
508255570Strasz	login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
509255570Strasz
510255570Strasz	/*
511255570Strasz	 * Actually, one more thing: mutual authentication.
512255570Strasz	 */
513255570Strasz	request_keys = keys_new();
514255570Strasz	keys_load(request_keys, request);
515255570Strasz	chap_i = keys_find(request_keys, "CHAP_I");
516255570Strasz	chap_c = keys_find(request_keys, "CHAP_C");
517255570Strasz	if (chap_i != NULL || chap_c != NULL) {
518255570Strasz		if (chap_i == NULL) {
519255570Strasz			login_send_error(request, 0x02, 0x07);
520255570Strasz			log_errx(1, "initiator requested target "
521255570Strasz			    "authentication, but didn't send CHAP_I");
522255570Strasz		}
523255570Strasz		if (chap_c == NULL) {
524255570Strasz			login_send_error(request, 0x02, 0x07);
525255570Strasz			log_errx(1, "initiator requested target "
526255570Strasz			    "authentication, but didn't send CHAP_C");
527255570Strasz		}
528255570Strasz		if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) {
529255570Strasz			login_send_error(request, 0x02, 0x01);
530255570Strasz			log_errx(1, "initiator requests target authentication "
531255570Strasz			    "for user \"%s\", but mutual user/secret "
532255570Strasz			    "is not set", auth->a_user);
533255570Strasz		}
534255570Strasz
535255570Strasz		id = strtoul(chap_i, NULL, 10);
536255570Strasz		error = login_hex2bin(chap_c, &challenge, &challenge_len);
537255570Strasz		if (error != 0) {
538255570Strasz			login_send_error(request, 0x02, 0x07);
539255570Strasz			log_errx(1, "received CHAP Login PDU with malformed "
540255570Strasz			    "CHAP_C");
541255570Strasz		}
542255570Strasz
543255570Strasz		log_debugx("performing mutual authentication as user \"%s\"",
544255570Strasz		    auth->a_mutual_user);
545255570Strasz		login_compute_md5(id, auth->a_mutual_secret, challenge,
546255570Strasz		    challenge_len, response_bin, sizeof(response_bin));
547255570Strasz
548255570Strasz		chap_r = login_bin2hex(response_bin,
549255570Strasz		    sizeof(response_bin));
550255570Strasz		response_keys = keys_new();
551255570Strasz		keys_add(response_keys, "CHAP_N", auth->a_mutual_user);
552255570Strasz		keys_add(response_keys, "CHAP_R", chap_r);
553255570Strasz		free(chap_r);
554255570Strasz		keys_save(response_keys, response);
555255570Strasz		keys_delete(response_keys);
556255570Strasz	} else {
557255570Strasz		log_debugx("initiator did not request target authentication");
558255570Strasz	}
559255570Strasz
560255570Strasz	keys_delete(request_keys);
561255570Strasz	pdu_send(response);
562256192Strasz	pdu_delete(response);
563255570Strasz}
564255570Strasz
565255570Straszstatic void
566255570Straszlogin_chap(struct connection *conn, struct auth_group *ag)
567255570Strasz{
568255570Strasz	const struct auth *auth;
569255570Strasz	struct pdu *request;
570255570Strasz	char challenge_bin[LOGIN_CHALLENGE_LEN];
571255570Strasz	unsigned char id;
572255570Strasz	int rv;
573255570Strasz
574255570Strasz	/*
575255570Strasz	 * Receive CHAP_A PDU.
576255570Strasz	 */
577255570Strasz	log_debugx("beginning CHAP authentication; waiting for CHAP_A");
578255570Strasz	request = login_receive_chap_a(conn);
579255570Strasz
580255570Strasz	/*
581255570Strasz	 * Generate the challenge.
582255570Strasz	 */
583255570Strasz	rv = RAND_bytes(challenge_bin, sizeof(challenge_bin));
584255570Strasz	if (rv != 1) {
585255570Strasz		login_send_error(request, 0x03, 0x02);
586255570Strasz		log_errx(1, "RAND_bytes failed: %s",
587255570Strasz		    ERR_error_string(ERR_get_error(), NULL));
588255570Strasz	}
589255570Strasz	rv = RAND_bytes(&id, sizeof(id));
590255570Strasz	if (rv != 1) {
591255570Strasz		login_send_error(request, 0x03, 0x02);
592255570Strasz		log_errx(1, "RAND_bytes failed: %s",
593255570Strasz		    ERR_error_string(ERR_get_error(), NULL));
594255570Strasz	}
595255570Strasz
596255570Strasz	/*
597255570Strasz	 * Send the challenge.
598255570Strasz	 */
599255570Strasz	log_debugx("sending CHAP_C, binary challenge size is %zd bytes",
600255570Strasz	    sizeof(challenge_bin));
601255570Strasz	login_send_chap_c(request, id, challenge_bin,
602255570Strasz	    sizeof(challenge_bin));
603255570Strasz	pdu_delete(request);
604255570Strasz
605255570Strasz	/*
606255570Strasz	 * Receive CHAP_N/CHAP_R PDU and authenticate.
607255570Strasz	 */
608255570Strasz	log_debugx("waiting for CHAP_N/CHAP_R");
609255570Strasz	request = login_receive_chap_r(conn, ag, id, challenge_bin,
610255570Strasz	    sizeof(challenge_bin), &auth);
611255570Strasz
612255570Strasz	/*
613255570Strasz	 * Yay, authentication succeeded!
614255570Strasz	 */
615255570Strasz	log_debugx("authentication succeeded for user \"%s\"; "
616255570Strasz	    "transitioning to Negotiation Phase", auth->a_user);
617255570Strasz	login_send_chap_success(request, auth);
618255570Strasz	pdu_delete(request);
619255570Strasz}
620255570Strasz
621255570Straszstatic void
622255570Straszlogin_negotiate_key(struct pdu *request, const char *name,
623255570Strasz    const char *value, bool skipped_security, struct keys *response_keys)
624255570Strasz{
625255570Strasz	int which, tmp;
626255570Strasz	struct connection *conn;
627255570Strasz
628255570Strasz	conn = request->pdu_connection;
629255570Strasz
630255570Strasz	if (strcmp(name, "InitiatorName") == 0) {
631255570Strasz		if (!skipped_security)
632255570Strasz			log_errx(1, "initiator resent InitiatorName");
633255570Strasz	} else if (strcmp(name, "SessionType") == 0) {
634255570Strasz		if (!skipped_security)
635255570Strasz			log_errx(1, "initiator resent SessionType");
636255570Strasz	} else if (strcmp(name, "TargetName") == 0) {
637255570Strasz		if (!skipped_security)
638255570Strasz			log_errx(1, "initiator resent TargetName");
639255570Strasz	} else if (strcmp(name, "InitiatorAlias") == 0) {
640255570Strasz		if (conn->conn_initiator_alias != NULL)
641255570Strasz			free(conn->conn_initiator_alias);
642255570Strasz		conn->conn_initiator_alias = checked_strdup(value);
643255570Strasz	} else if (strcmp(value, "Irrelevant") == 0) {
644255570Strasz		/* Ignore. */
645255570Strasz	} else if (strcmp(name, "HeaderDigest") == 0) {
646255570Strasz		/*
647255570Strasz		 * We don't handle digests for discovery sessions.
648255570Strasz		 */
649255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
650255570Strasz			log_debugx("discovery session; digests disabled");
651255570Strasz			keys_add(response_keys, name, "None");
652255570Strasz			return;
653255570Strasz		}
654255570Strasz
655255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
656255570Strasz		switch (which) {
657255570Strasz		case 1:
658255570Strasz			log_debugx("initiator prefers CRC32C "
659255570Strasz			    "for header digest; we'll use it");
660255570Strasz			conn->conn_header_digest = CONN_DIGEST_CRC32C;
661255570Strasz			keys_add(response_keys, name, "CRC32C");
662255570Strasz			break;
663255570Strasz		case 2:
664255570Strasz			log_debugx("initiator prefers not to do "
665255570Strasz			    "header digest; we'll comply");
666255570Strasz			keys_add(response_keys, name, "None");
667255570Strasz			break;
668255570Strasz		default:
669255570Strasz			log_warnx("initiator sent unrecognized "
670255570Strasz			    "HeaderDigest value \"%s\"; will use None", value);
671255570Strasz			keys_add(response_keys, name, "None");
672255570Strasz			break;
673255570Strasz		}
674255570Strasz	} else if (strcmp(name, "DataDigest") == 0) {
675255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
676255570Strasz			log_debugx("discovery session; digests disabled");
677255570Strasz			keys_add(response_keys, name, "None");
678255570Strasz			return;
679255570Strasz		}
680255570Strasz
681255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
682255570Strasz		switch (which) {
683255570Strasz		case 1:
684255570Strasz			log_debugx("initiator prefers CRC32C "
685255570Strasz			    "for data digest; we'll use it");
686255570Strasz			conn->conn_data_digest = CONN_DIGEST_CRC32C;
687255570Strasz			keys_add(response_keys, name, "CRC32C");
688255570Strasz			break;
689255570Strasz		case 2:
690255570Strasz			log_debugx("initiator prefers not to do "
691255570Strasz			    "data digest; we'll comply");
692255570Strasz			keys_add(response_keys, name, "None");
693255570Strasz			break;
694255570Strasz		default:
695255570Strasz			log_warnx("initiator sent unrecognized "
696255570Strasz			    "DataDigest value \"%s\"; will use None", value);
697255570Strasz			keys_add(response_keys, name, "None");
698255570Strasz			break;
699255570Strasz		}
700255570Strasz	} else if (strcmp(name, "MaxConnections") == 0) {
701255570Strasz		keys_add(response_keys, name, "1");
702255570Strasz	} else if (strcmp(name, "InitialR2T") == 0) {
703255570Strasz		keys_add(response_keys, name, "Yes");
704255570Strasz	} else if (strcmp(name, "ImmediateData") == 0) {
705255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
706255570Strasz			log_debugx("discovery session; ImmediateData irrelevant");
707255570Strasz			keys_add(response_keys, name, "Irrelevant");
708255570Strasz		} else {
709255570Strasz			if (strcmp(value, "Yes") == 0) {
710255570Strasz				conn->conn_immediate_data = true;
711255570Strasz				keys_add(response_keys, name, "Yes");
712255570Strasz			} else {
713255570Strasz				conn->conn_immediate_data = false;
714255570Strasz				keys_add(response_keys, name, "No");
715255570Strasz			}
716255570Strasz		}
717255570Strasz	} else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
718255570Strasz		tmp = strtoul(value, NULL, 10);
719255570Strasz		if (tmp <= 0) {
720255570Strasz			login_send_error(request, 0x02, 0x00);
721255570Strasz			log_errx(1, "received invalid "
722255570Strasz			    "MaxRecvDataSegmentLength");
723255570Strasz		}
724255570Strasz		if (tmp > MAX_DATA_SEGMENT_LENGTH) {
725255570Strasz			log_debugx("capping MaxDataSegmentLength from %d to %d",
726255570Strasz			    tmp, MAX_DATA_SEGMENT_LENGTH);
727255570Strasz			tmp = MAX_DATA_SEGMENT_LENGTH;
728255570Strasz		}
729255570Strasz		conn->conn_max_data_segment_length = tmp;
730255570Strasz		keys_add_int(response_keys, name, tmp);
731255570Strasz	} else if (strcmp(name, "MaxBurstLength") == 0) {
732255570Strasz		tmp = strtoul(value, NULL, 10);
733255570Strasz		if (tmp <= 0) {
734255570Strasz			login_send_error(request, 0x02, 0x00);
735255570Strasz			log_errx(1, "received invalid MaxBurstLength");
736255570Strasz		}
737255570Strasz		if (tmp > MAX_BURST_LENGTH) {
738255570Strasz			log_debugx("capping MaxBurstLength from %d to %d",
739255570Strasz			    tmp, MAX_BURST_LENGTH);
740255570Strasz			tmp = MAX_BURST_LENGTH;
741255570Strasz		}
742255570Strasz		conn->conn_max_burst_length = tmp;
743255570Strasz		keys_add(response_keys, name, value);
744255570Strasz	} else if (strcmp(name, "FirstBurstLength") == 0) {
745255570Strasz		tmp = strtoul(value, NULL, 10);
746255570Strasz		if (tmp <= 0) {
747255570Strasz			login_send_error(request, 0x02, 0x00);
748255570Strasz			log_errx(1, "received invalid "
749255570Strasz			    "FirstBurstLength");
750255570Strasz		}
751255570Strasz		if (tmp > MAX_DATA_SEGMENT_LENGTH) {
752255570Strasz			log_debugx("capping FirstBurstLength from %d to %d",
753255570Strasz			    tmp, MAX_DATA_SEGMENT_LENGTH);
754255570Strasz			tmp = MAX_DATA_SEGMENT_LENGTH;
755255570Strasz		}
756255570Strasz		/*
757255570Strasz		 * We don't pass the value to the kernel; it only enforces
758255570Strasz		 * hardcoded limit anyway.
759255570Strasz		 */
760255570Strasz		keys_add_int(response_keys, name, tmp);
761255570Strasz	} else if (strcmp(name, "DefaultTime2Wait") == 0) {
762255570Strasz		keys_add(response_keys, name, value);
763255570Strasz	} else if (strcmp(name, "DefaultTime2Retain") == 0) {
764255570Strasz		keys_add(response_keys, name, "0");
765255570Strasz	} else if (strcmp(name, "MaxOutstandingR2T") == 0) {
766255570Strasz		keys_add(response_keys, name, "1");
767255570Strasz	} else if (strcmp(name, "DataPDUInOrder") == 0) {
768255570Strasz		keys_add(response_keys, name, "Yes");
769255570Strasz	} else if (strcmp(name, "DataSequenceInOrder") == 0) {
770255570Strasz		keys_add(response_keys, name, "Yes");
771255570Strasz	} else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
772255570Strasz		keys_add(response_keys, name, "0");
773255570Strasz	} else if (strcmp(name, "OFMarker") == 0) {
774255570Strasz		keys_add(response_keys, name, "No");
775255570Strasz	} else if (strcmp(name, "IFMarker") == 0) {
776255570Strasz		keys_add(response_keys, name, "No");
777255570Strasz	} else {
778255570Strasz		log_debugx("unknown key \"%s\"; responding "
779255570Strasz		    "with NotUnderstood", name);
780255570Strasz		keys_add(response_keys, name, "NotUnderstood");
781255570Strasz	}
782255570Strasz}
783255570Strasz
784255570Straszstatic void
785255570Straszlogin_negotiate(struct connection *conn, struct pdu *request)
786255570Strasz{
787255570Strasz	struct pdu *response;
788255570Strasz	struct iscsi_bhs_login_response *bhslr2;
789255570Strasz	struct keys *request_keys, *response_keys;
790255570Strasz	int i;
791255570Strasz	bool skipped_security;
792255570Strasz
793255570Strasz	if (request == NULL) {
794255570Strasz		log_debugx("beginning parameter negotiation; "
795255570Strasz		    "waiting for Login PDU");
796255570Strasz		request = login_receive(conn, false);
797255570Strasz		skipped_security = false;
798255570Strasz	} else
799255570Strasz		skipped_security = true;
800255570Strasz
801255570Strasz	request_keys = keys_new();
802255570Strasz	keys_load(request_keys, request);
803255570Strasz
804255570Strasz	response = login_new_response(request);
805255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
806255570Strasz	bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
807255570Strasz	bhslr2->bhslr_tsih = htons(0xbadd);
808255570Strasz	login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
809255570Strasz	login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE);
810255570Strasz	response_keys = keys_new();
811255570Strasz	for (i = 0; i < KEYS_MAX; i++) {
812255570Strasz		if (request_keys->keys_names[i] == NULL)
813255570Strasz			break;
814255570Strasz
815255570Strasz		login_negotiate_key(request, request_keys->keys_names[i],
816255570Strasz		    request_keys->keys_values[i], skipped_security,
817255570Strasz		    response_keys);
818255570Strasz	}
819255570Strasz
820255570Strasz	log_debugx("parameter negotiation done; "
821255570Strasz	    "transitioning to Full Feature Phase");
822255570Strasz
823255570Strasz	keys_save(response_keys, response);
824255570Strasz	pdu_send(response);
825255570Strasz	pdu_delete(response);
826255570Strasz	keys_delete(response_keys);
827255570Strasz	pdu_delete(request);
828255570Strasz	keys_delete(request_keys);
829255570Strasz}
830255570Strasz
831255570Straszvoid
832255570Straszlogin(struct connection *conn)
833255570Strasz{
834255570Strasz	struct pdu *request, *response;
835255570Strasz	struct iscsi_bhs_login_request *bhslr;
836255570Strasz	struct iscsi_bhs_login_response *bhslr2;
837255570Strasz	struct keys *request_keys, *response_keys;
838255570Strasz	struct auth_group *ag;
839255570Strasz	const char *initiator_name, *initiator_alias, *session_type,
840255570Strasz	    *target_name, *auth_method;
841255570Strasz	char *portal_group_tag;
842255570Strasz	int rv;
843255570Strasz
844255570Strasz	/*
845255570Strasz	 * Handle the initial Login Request - figure out required authentication
846255570Strasz	 * method and either transition to the next phase, if no authentication
847255570Strasz	 * is required, or call appropriate authentication code.
848255570Strasz	 */
849255570Strasz	log_debugx("beginning Login Phase; waiting for Login PDU");
850255570Strasz	request = login_receive(conn, true);
851255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
852255570Strasz	if (bhslr->bhslr_tsih != 0) {
853255570Strasz		login_send_error(request, 0x02, 0x0a);
854255570Strasz		log_errx(1, "received Login PDU with non-zero TSIH");
855255570Strasz	}
856255570Strasz
857255570Strasz	/*
858255570Strasz	 * XXX: Implement the C flag some day.
859255570Strasz	 */
860255570Strasz	request_keys = keys_new();
861255570Strasz	keys_load(request_keys, request);
862255570Strasz
863255570Strasz	assert(conn->conn_initiator_name == NULL);
864255570Strasz	initiator_name = keys_find(request_keys, "InitiatorName");
865255570Strasz	if (initiator_name == NULL) {
866255570Strasz		login_send_error(request, 0x02, 0x07);
867255570Strasz		log_errx(1, "received Login PDU without InitiatorName");
868255570Strasz	}
869255570Strasz	if (valid_iscsi_name(initiator_name) == false) {
870255570Strasz		login_send_error(request, 0x02, 0x00);
871255570Strasz		log_errx(1, "received Login PDU with invalid InitiatorName");
872255570Strasz	}
873255570Strasz	conn->conn_initiator_name = checked_strdup(initiator_name);
874255570Strasz	log_set_peer_name(conn->conn_initiator_name);
875255570Strasz	/*
876255570Strasz	 * XXX: This doesn't work (does nothing) because of Capsicum.
877255570Strasz	 */
878255570Strasz	setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name);
879255570Strasz
880255570Strasz	initiator_alias = keys_find(request_keys, "InitiatorAlias");
881255570Strasz	if (initiator_alias != NULL)
882255570Strasz		conn->conn_initiator_alias = checked_strdup(initiator_alias);
883255570Strasz
884255570Strasz	assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE);
885255570Strasz	session_type = keys_find(request_keys, "SessionType");
886255570Strasz	if (session_type != NULL) {
887255570Strasz		if (strcmp(session_type, "Normal") == 0) {
888255570Strasz			conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
889255570Strasz		} else if (strcmp(session_type, "Discovery") == 0) {
890255570Strasz			conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY;
891255570Strasz		} else {
892255570Strasz			login_send_error(request, 0x02, 0x00);
893255570Strasz			log_errx(1, "received Login PDU with invalid "
894255570Strasz			    "SessionType \"%s\"", session_type);
895255570Strasz		}
896255570Strasz	} else
897255570Strasz		conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
898255570Strasz
899255570Strasz	assert(conn->conn_target == NULL);
900255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
901255570Strasz		target_name = keys_find(request_keys, "TargetName");
902255570Strasz		if (target_name == NULL) {
903255570Strasz			login_send_error(request, 0x02, 0x07);
904255570Strasz			log_errx(1, "received Login PDU without TargetName");
905255570Strasz		}
906255570Strasz
907255570Strasz		conn->conn_target =
908255570Strasz		    target_find(conn->conn_portal->p_portal_group->pg_conf,
909255570Strasz		    target_name);
910255570Strasz		if (conn->conn_target == NULL) {
911255570Strasz			login_send_error(request, 0x02, 0x03);
912255570Strasz			log_errx(1, "requested target \"%s\" not found",
913255570Strasz			    target_name);
914255570Strasz		}
915255570Strasz	}
916255570Strasz
917255570Strasz	/*
918255570Strasz	 * At this point we know what kind of authentication we need.
919255570Strasz	 */
920255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
921255570Strasz		ag = conn->conn_target->t_auth_group;
922255570Strasz		if (ag->ag_name != NULL) {
923255570Strasz			log_debugx("initiator requests to connect "
924255570Strasz			    "to target \"%s\"; auth-group \"%s\"",
925255570Strasz			    conn->conn_target->t_iqn,
926255570Strasz			    conn->conn_target->t_auth_group->ag_name);
927255570Strasz		} else {
928255570Strasz			log_debugx("initiator requests to connect "
929255570Strasz			    "to target \"%s\"", conn->conn_target->t_iqn);
930255570Strasz		}
931255570Strasz	} else {
932255570Strasz		assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
933255570Strasz		ag = conn->conn_portal->p_portal_group->pg_discovery_auth_group;
934255570Strasz		if (ag->ag_name != NULL) {
935255570Strasz			log_debugx("initiator requests "
936255570Strasz			    "discovery session; auth-group \"%s\"", ag->ag_name);
937255570Strasz		} else {
938255570Strasz			log_debugx("initiator requests discovery session");
939255570Strasz		}
940255570Strasz	}
941255570Strasz
942255570Strasz	/*
943255570Strasz	 * Let's see if the initiator intends to do any kind of authentication
944255570Strasz	 * at all.
945255570Strasz	 */
946255570Strasz	if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
947255570Strasz		if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
948255570Strasz			login_send_error(request, 0x02, 0x01);
949255570Strasz			log_errx(1, "initiator skipped the authentication, "
950255570Strasz			    "but authentication is required");
951255570Strasz		}
952255570Strasz
953255570Strasz		keys_delete(request_keys);
954255570Strasz
955255570Strasz		log_debugx("initiator skipped the authentication, "
956255570Strasz		    "and we don't need it; proceeding with negotiation");
957255570Strasz		login_negotiate(conn, request);
958255570Strasz		return;
959255570Strasz	}
960255570Strasz
961255570Strasz	if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) {
962255570Strasz		/*
963255570Strasz		 * Initiator might want to to authenticate,
964255570Strasz		 * but we don't need it.
965255570Strasz		 */
966255570Strasz		log_debugx("authentication not required; "
967255570Strasz		    "transitioning to parameter negotiation");
968255570Strasz
969255570Strasz		if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0)
970255570Strasz			log_warnx("initiator did not set the \"T\" flag; "
971255570Strasz			    "transitioning anyway");
972255570Strasz
973255570Strasz		response = login_new_response(request);
974255570Strasz		bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
975255570Strasz		bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
976255570Strasz		login_set_nsg(response,
977255570Strasz		    BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
978255570Strasz		response_keys = keys_new();
979255570Strasz		/*
980255570Strasz		 * Required by Linux initiator.
981255570Strasz		 */
982255570Strasz		auth_method = keys_find(request_keys, "AuthMethod");
983255570Strasz		if (auth_method != NULL &&
984255570Strasz		    login_list_contains(auth_method, "None"))
985255570Strasz			keys_add(response_keys, "AuthMethod", "None");
986255570Strasz
987255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
988255570Strasz			if (conn->conn_target->t_alias != NULL)
989255570Strasz				keys_add(response_keys,
990255570Strasz				    "TargetAlias", conn->conn_target->t_alias);
991255570Strasz			rv = asprintf(&portal_group_tag, "%d",
992255570Strasz			    conn->conn_portal->p_portal_group->pg_tag);
993255570Strasz			if (rv <= 0)
994255570Strasz				log_err(1, "asprintf");
995255570Strasz			keys_add(response_keys,
996255570Strasz			    "TargetPortalGroupTag", portal_group_tag);
997255570Strasz			free(portal_group_tag);
998255570Strasz		}
999255570Strasz		keys_save(response_keys, response);
1000255570Strasz		pdu_send(response);
1001255570Strasz		pdu_delete(response);
1002255570Strasz		keys_delete(response_keys);
1003255570Strasz		pdu_delete(request);
1004255570Strasz		keys_delete(request_keys);
1005255570Strasz
1006255570Strasz		login_negotiate(conn, NULL);
1007255570Strasz		return;
1008255570Strasz	}
1009255570Strasz
1010259333Strasz	if (ag->ag_type == AG_TYPE_UNKNOWN) {
1011259333Strasz		/*
1012259333Strasz		 * This can happen with empty auth-group.
1013259333Strasz		 */
1014259333Strasz		login_send_error(request, 0x02, 0x01);
1015259333Strasz		log_errx(1, "auth-group type not set, denying access");
1016259333Strasz	}
1017259333Strasz
1018255570Strasz	log_debugx("CHAP authentication required");
1019255570Strasz
1020255570Strasz	auth_method = keys_find(request_keys, "AuthMethod");
1021255570Strasz	if (auth_method == NULL) {
1022255570Strasz		login_send_error(request, 0x02, 0x07);
1023255570Strasz		log_errx(1, "received Login PDU without AuthMethod");
1024255570Strasz	}
1025255570Strasz	/*
1026255570Strasz	 * XXX: This should be Reject, not just a login failure (5.3.2).
1027255570Strasz	 */
1028255570Strasz	if (login_list_contains(auth_method, "CHAP") == 0) {
1029255570Strasz		login_send_error(request, 0x02, 0x01);
1030255570Strasz		log_errx(1, "initiator requests unsupported AuthMethod \"%s\" "
1031255570Strasz		    "instead of \"CHAP\"", auth_method);
1032255570Strasz	}
1033255570Strasz
1034255570Strasz	response = login_new_response(request);
1035255570Strasz
1036255570Strasz	response_keys = keys_new();
1037255570Strasz	keys_add(response_keys, "AuthMethod", "CHAP");
1038255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
1039255570Strasz		rv = asprintf(&portal_group_tag, "%d",
1040255570Strasz		    conn->conn_portal->p_portal_group->pg_tag);
1041255570Strasz		if (rv <= 0)
1042255570Strasz			log_err(1, "asprintf");
1043255570Strasz		keys_add(response_keys,
1044255570Strasz		    "TargetPortalGroupTag", portal_group_tag);
1045255570Strasz		free(portal_group_tag);
1046255570Strasz		if (conn->conn_target->t_alias != NULL)
1047255570Strasz			keys_add(response_keys,
1048255570Strasz			    "TargetAlias", conn->conn_target->t_alias);
1049255570Strasz	}
1050255570Strasz	keys_save(response_keys, response);
1051255570Strasz
1052255570Strasz	pdu_send(response);
1053255570Strasz	pdu_delete(response);
1054255570Strasz	keys_delete(response_keys);
1055255570Strasz	pdu_delete(request);
1056255570Strasz	keys_delete(request_keys);
1057255570Strasz
1058255570Strasz	login_chap(conn, ag);
1059255570Strasz
1060255570Strasz	login_negotiate(conn, NULL);
1061255570Strasz}
1062