login.c revision 270137
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: stable/10/usr.sbin/ctld/login.c 270137 2014-08-18 16:06:04Z mav $
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 (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) {
132255570Strasz		login_send_error(request, 0x02, 0x05);
133255570Strasz		log_errx(1, "received Login PDU with decreasing CmdSN: "
134255570Strasz		    "was %d, is %d", conn->conn_cmdsn,
135255570Strasz		    ntohl(bhslr->bhslr_cmdsn));
136255570Strasz	}
137255570Strasz	if (initial == false &&
138255570Strasz	    ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
139255570Strasz		login_send_error(request, 0x02, 0x05);
140255570Strasz		log_errx(1, "received Login PDU with wrong ExpStatSN: "
141255570Strasz		    "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn),
142255570Strasz		    conn->conn_statsn);
143255570Strasz	}
144255570Strasz	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
145255570Strasz
146255570Strasz	return (request);
147255570Strasz}
148255570Strasz
149255570Straszstatic struct pdu *
150255570Straszlogin_new_response(struct pdu *request)
151255570Strasz{
152255570Strasz	struct pdu *response;
153255570Strasz	struct connection *conn;
154255570Strasz	struct iscsi_bhs_login_request *bhslr;
155255570Strasz	struct iscsi_bhs_login_response *bhslr2;
156255570Strasz
157255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
158255570Strasz	conn = request->pdu_connection;
159255570Strasz
160255570Strasz	response = pdu_new_response(request);
161255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
162255570Strasz	bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE;
163255570Strasz	login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION);
164255570Strasz	memcpy(bhslr2->bhslr_isid,
165255570Strasz	    bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid));
166255570Strasz	bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
167255570Strasz	bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
168255570Strasz	bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
169255570Strasz	bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
170255570Strasz
171255570Strasz	return (response);
172255570Strasz}
173255570Strasz
174255570Straszstatic void
175255570Straszlogin_send_error(struct pdu *request, char class, char detail)
176255570Strasz{
177255570Strasz	struct pdu *response;
178255570Strasz	struct iscsi_bhs_login_response *bhslr2;
179255570Strasz
180255570Strasz	log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; "
181255570Strasz	    "see next line for reason", class, detail);
182255570Strasz	response = login_new_response(request);
183255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
184255570Strasz	bhslr2->bhslr_status_class = class;
185255570Strasz	bhslr2->bhslr_status_detail = detail;
186255570Strasz
187255570Strasz	pdu_send(response);
188255570Strasz	pdu_delete(response);
189255570Strasz}
190255570Strasz
191255570Straszstatic int
192255570Straszlogin_list_contains(const char *list, const char *what)
193255570Strasz{
194255570Strasz	char *tofree, *str, *token;
195255570Strasz
196255570Strasz	tofree = str = checked_strdup(list);
197255570Strasz
198255570Strasz	while ((token = strsep(&str, ",")) != NULL) {
199255570Strasz		if (strcmp(token, what) == 0) {
200255570Strasz			free(tofree);
201255570Strasz			return (1);
202255570Strasz		}
203255570Strasz	}
204255570Strasz	free(tofree);
205255570Strasz	return (0);
206255570Strasz}
207255570Strasz
208255570Straszstatic int
209255570Straszlogin_list_prefers(const char *list,
210255570Strasz    const char *choice1, const char *choice2)
211255570Strasz{
212255570Strasz	char *tofree, *str, *token;
213255570Strasz
214255570Strasz	tofree = str = checked_strdup(list);
215255570Strasz
216255570Strasz	while ((token = strsep(&str, ",")) != NULL) {
217255570Strasz		if (strcmp(token, choice1) == 0) {
218255570Strasz			free(tofree);
219255570Strasz			return (1);
220255570Strasz		}
221255570Strasz		if (strcmp(token, choice2) == 0) {
222255570Strasz			free(tofree);
223255570Strasz			return (2);
224255570Strasz		}
225255570Strasz	}
226255570Strasz	free(tofree);
227255570Strasz	return (-1);
228255570Strasz}
229255570Strasz
230255570Straszstatic int
231255570Straszlogin_hex2int(const char hex)
232255570Strasz{
233255570Strasz	switch (hex) {
234255570Strasz	case '0':
235255570Strasz		return (0x00);
236255570Strasz	case '1':
237255570Strasz		return (0x01);
238255570Strasz	case '2':
239255570Strasz		return (0x02);
240255570Strasz	case '3':
241255570Strasz		return (0x03);
242255570Strasz	case '4':
243255570Strasz		return (0x04);
244255570Strasz	case '5':
245255570Strasz		return (0x05);
246255570Strasz	case '6':
247255570Strasz		return (0x06);
248255570Strasz	case '7':
249255570Strasz		return (0x07);
250255570Strasz	case '8':
251255570Strasz		return (0x08);
252255570Strasz	case '9':
253255570Strasz		return (0x09);
254255570Strasz	case 'a':
255255570Strasz	case 'A':
256255570Strasz		return (0x0a);
257255570Strasz	case 'b':
258255570Strasz	case 'B':
259255570Strasz		return (0x0b);
260255570Strasz	case 'c':
261255570Strasz	case 'C':
262255570Strasz		return (0x0c);
263255570Strasz	case 'd':
264255570Strasz	case 'D':
265255570Strasz		return (0x0d);
266255570Strasz	case 'e':
267255570Strasz	case 'E':
268255570Strasz		return (0x0e);
269255570Strasz	case 'f':
270255570Strasz	case 'F':
271255570Strasz		return (0x0f);
272255570Strasz	default:
273255570Strasz		return (-1);
274255570Strasz	}
275255570Strasz}
276255570Strasz
277255570Strasz/*
278255570Strasz * XXX: Review this _carefully_.
279255570Strasz */
280255570Straszstatic int
281255570Straszlogin_hex2bin(const char *hex, char **binp, size_t *bin_lenp)
282255570Strasz{
283255570Strasz	int i, hex_len, nibble;
284255570Strasz	bool lo = true; /* As opposed to 'hi'. */
285255570Strasz	char *bin;
286255570Strasz	size_t bin_off, bin_len;
287255570Strasz
288255570Strasz	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
289255570Strasz		log_warnx("malformed variable, should start with \"0x\"");
290255570Strasz		return (-1);
291255570Strasz	}
292255570Strasz
293255570Strasz	hex += strlen("0x");
294255570Strasz	hex_len = strlen(hex);
295255570Strasz	if (hex_len < 1) {
296255570Strasz		log_warnx("malformed variable; doesn't contain anything "
297255570Strasz		    "but \"0x\"");
298255570Strasz		return (-1);
299255570Strasz	}
300255570Strasz
301255570Strasz	bin_len = hex_len / 2 + hex_len % 2;
302255570Strasz	bin = calloc(bin_len, 1);
303255570Strasz	if (bin == NULL)
304255570Strasz		log_err(1, "calloc");
305255570Strasz
306255570Strasz	bin_off = bin_len - 1;
307255570Strasz	for (i = hex_len - 1; i >= 0; i--) {
308255570Strasz		nibble = login_hex2int(hex[i]);
309255570Strasz		if (nibble < 0) {
310255570Strasz			log_warnx("malformed variable, invalid char \"%c\"",
311255570Strasz			    hex[i]);
312255570Strasz			return (-1);
313255570Strasz		}
314255570Strasz
315255570Strasz		assert(bin_off < bin_len);
316255570Strasz		if (lo) {
317255570Strasz			bin[bin_off] = nibble;
318255570Strasz			lo = false;
319255570Strasz		} else {
320255570Strasz			bin[bin_off] |= nibble << 4;
321255570Strasz			bin_off--;
322255570Strasz			lo = true;
323255570Strasz		}
324255570Strasz	}
325255570Strasz
326255570Strasz	*binp = bin;
327255570Strasz	*bin_lenp = bin_len;
328255570Strasz	return (0);
329255570Strasz}
330255570Strasz
331255570Straszstatic char *
332255570Straszlogin_bin2hex(const char *bin, size_t bin_len)
333255570Strasz{
334255570Strasz	unsigned char *hex, *tmp, ch;
335255570Strasz	size_t hex_len;
336255570Strasz	size_t i;
337255570Strasz
338255570Strasz	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
339255570Strasz	hex = malloc(hex_len);
340255570Strasz	if (hex == NULL)
341255570Strasz		log_err(1, "malloc");
342255570Strasz
343255570Strasz	tmp = hex;
344255570Strasz	tmp += sprintf(tmp, "0x");
345255570Strasz	for (i = 0; i < bin_len; i++) {
346255570Strasz		ch = bin[i];
347255570Strasz		tmp += sprintf(tmp, "%02x", ch);
348255570Strasz	}
349255570Strasz
350255570Strasz	return (hex);
351255570Strasz}
352255570Strasz
353255570Straszstatic void
354255570Straszlogin_compute_md5(const char id, const char *secret,
355255570Strasz    const void *challenge, size_t challenge_len, void *response,
356255570Strasz    size_t response_len)
357255570Strasz{
358255570Strasz	MD5_CTX ctx;
359255570Strasz	int rv;
360255570Strasz
361255570Strasz	assert(response_len == MD5_DIGEST_LENGTH);
362255570Strasz
363255570Strasz	MD5_Init(&ctx);
364255570Strasz	MD5_Update(&ctx, &id, sizeof(id));
365255570Strasz	MD5_Update(&ctx, secret, strlen(secret));
366255570Strasz	MD5_Update(&ctx, challenge, challenge_len);
367255570Strasz	rv = MD5_Final(response, &ctx);
368255570Strasz	if (rv != 1)
369255570Strasz		log_errx(1, "MD5_Final");
370255570Strasz}
371255570Strasz
372255570Strasz#define	LOGIN_CHALLENGE_LEN	1024
373255570Strasz
374255570Straszstatic struct pdu *
375255570Straszlogin_receive_chap_a(struct connection *conn)
376255570Strasz{
377255570Strasz	struct pdu *request;
378255570Strasz	struct keys *request_keys;
379255570Strasz	const char *chap_a;
380255570Strasz
381255570Strasz	request = login_receive(conn, false);
382255570Strasz	request_keys = keys_new();
383255570Strasz	keys_load(request_keys, request);
384255570Strasz
385255570Strasz	chap_a = keys_find(request_keys, "CHAP_A");
386255570Strasz	if (chap_a == NULL) {
387255570Strasz		login_send_error(request, 0x02, 0x07);
388255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_A");
389255570Strasz	}
390255570Strasz	if (login_list_contains(chap_a, "5") == 0) {
391255570Strasz		login_send_error(request, 0x02, 0x01);
392255570Strasz		log_errx(1, "received CHAP Login PDU with unsupported CHAP_A "
393255570Strasz		    "\"%s\"", chap_a);
394255570Strasz	}
395255570Strasz	keys_delete(request_keys);
396255570Strasz
397255570Strasz	return (request);
398255570Strasz}
399255570Strasz
400255570Straszstatic void
401255570Straszlogin_send_chap_c(struct pdu *request, const unsigned char id,
402255570Strasz    const void *challenge, const size_t challenge_len)
403255570Strasz{
404255570Strasz	struct pdu *response;
405255570Strasz	struct keys *response_keys;
406255570Strasz	char *chap_c, chap_i[4];
407255570Strasz
408255570Strasz	chap_c = login_bin2hex(challenge, challenge_len);
409255570Strasz	snprintf(chap_i, sizeof(chap_i), "%d", id);
410255570Strasz
411255570Strasz	response = login_new_response(request);
412255570Strasz	response_keys = keys_new();
413255570Strasz	keys_add(response_keys, "CHAP_A", "5");
414255570Strasz	keys_add(response_keys, "CHAP_I", chap_i);
415255570Strasz	keys_add(response_keys, "CHAP_C", chap_c);
416255570Strasz	free(chap_c);
417255570Strasz	keys_save(response_keys, response);
418256192Strasz	pdu_send(response);
419256192Strasz	pdu_delete(response);
420255570Strasz	keys_delete(response_keys);
421255570Strasz}
422255570Strasz
423255570Straszstatic struct pdu *
424255570Straszlogin_receive_chap_r(struct connection *conn,
425255570Strasz    struct auth_group *ag, const unsigned char id, const void *challenge,
426255570Strasz    const size_t challenge_len, const struct auth **cap)
427255570Strasz{
428255570Strasz	struct pdu *request;
429255570Strasz	struct keys *request_keys;
430255570Strasz	const char *chap_n, *chap_r;
431255570Strasz	char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH];
432255570Strasz	size_t response_bin_len;
433255570Strasz	const struct auth *auth;
434255570Strasz	int error;
435255570Strasz
436255570Strasz	request = login_receive(conn, false);
437255570Strasz	request_keys = keys_new();
438255570Strasz	keys_load(request_keys, request);
439255570Strasz
440255570Strasz	chap_n = keys_find(request_keys, "CHAP_N");
441255570Strasz	if (chap_n == NULL) {
442255570Strasz		login_send_error(request, 0x02, 0x07);
443255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_N");
444255570Strasz	}
445255570Strasz	chap_r = keys_find(request_keys, "CHAP_R");
446255570Strasz	if (chap_r == NULL) {
447255570Strasz		login_send_error(request, 0x02, 0x07);
448255570Strasz		log_errx(1, "received CHAP Login PDU without CHAP_R");
449255570Strasz	}
450255570Strasz	error = login_hex2bin(chap_r, &response_bin, &response_bin_len);
451255570Strasz	if (error != 0) {
452255570Strasz		login_send_error(request, 0x02, 0x07);
453255570Strasz		log_errx(1, "received CHAP Login PDU with malformed CHAP_R");
454255570Strasz	}
455255570Strasz
456255570Strasz	/*
457255570Strasz	 * Verify the response.
458255570Strasz	 */
459255570Strasz	assert(ag->ag_type == AG_TYPE_CHAP ||
460255570Strasz	    ag->ag_type == AG_TYPE_CHAP_MUTUAL);
461255570Strasz	auth = auth_find(ag, chap_n);
462255570Strasz	if (auth == NULL) {
463255570Strasz		login_send_error(request, 0x02, 0x01);
464255570Strasz		log_errx(1, "received CHAP Login with invalid user \"%s\"",
465255570Strasz		    chap_n);
466255570Strasz	}
467255570Strasz
468255570Strasz	assert(auth->a_secret != NULL);
469255570Strasz	assert(strlen(auth->a_secret) > 0);
470255570Strasz	login_compute_md5(id, auth->a_secret, challenge,
471255570Strasz	    challenge_len, expected_response_bin,
472255570Strasz	    sizeof(expected_response_bin));
473255570Strasz
474255570Strasz	if (memcmp(response_bin, expected_response_bin,
475255570Strasz	    sizeof(expected_response_bin)) != 0) {
476255570Strasz		login_send_error(request, 0x02, 0x01);
477255570Strasz		log_errx(1, "CHAP authentication failed for user \"%s\"",
478255570Strasz		    auth->a_user);
479255570Strasz	}
480255570Strasz
481255570Strasz	keys_delete(request_keys);
482255570Strasz	free(response_bin);
483255570Strasz
484255570Strasz	*cap = auth;
485255570Strasz	return (request);
486255570Strasz}
487255570Strasz
488255570Straszstatic void
489255570Straszlogin_send_chap_success(struct pdu *request,
490255570Strasz    const struct auth *auth)
491255570Strasz{
492255570Strasz	struct pdu *response;
493255570Strasz	struct keys *request_keys, *response_keys;
494255570Strasz	struct iscsi_bhs_login_response *bhslr2;
495255570Strasz	const char *chap_i, *chap_c;
496255570Strasz	char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH];
497255570Strasz	size_t challenge_len;
498255570Strasz	unsigned char id;
499255570Strasz	int error;
500255570Strasz
501255570Strasz	response = login_new_response(request);
502255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
503255570Strasz	bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
504255570Strasz	login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
505255570Strasz
506255570Strasz	/*
507255570Strasz	 * Actually, one more thing: mutual authentication.
508255570Strasz	 */
509255570Strasz	request_keys = keys_new();
510255570Strasz	keys_load(request_keys, request);
511255570Strasz	chap_i = keys_find(request_keys, "CHAP_I");
512255570Strasz	chap_c = keys_find(request_keys, "CHAP_C");
513255570Strasz	if (chap_i != NULL || chap_c != NULL) {
514255570Strasz		if (chap_i == NULL) {
515255570Strasz			login_send_error(request, 0x02, 0x07);
516255570Strasz			log_errx(1, "initiator requested target "
517255570Strasz			    "authentication, but didn't send CHAP_I");
518255570Strasz		}
519255570Strasz		if (chap_c == NULL) {
520255570Strasz			login_send_error(request, 0x02, 0x07);
521255570Strasz			log_errx(1, "initiator requested target "
522255570Strasz			    "authentication, but didn't send CHAP_C");
523255570Strasz		}
524255570Strasz		if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) {
525255570Strasz			login_send_error(request, 0x02, 0x01);
526255570Strasz			log_errx(1, "initiator requests target authentication "
527255570Strasz			    "for user \"%s\", but mutual user/secret "
528255570Strasz			    "is not set", auth->a_user);
529255570Strasz		}
530255570Strasz
531255570Strasz		id = strtoul(chap_i, NULL, 10);
532255570Strasz		error = login_hex2bin(chap_c, &challenge, &challenge_len);
533255570Strasz		if (error != 0) {
534255570Strasz			login_send_error(request, 0x02, 0x07);
535255570Strasz			log_errx(1, "received CHAP Login PDU with malformed "
536255570Strasz			    "CHAP_C");
537255570Strasz		}
538255570Strasz
539255570Strasz		log_debugx("performing mutual authentication as user \"%s\"",
540255570Strasz		    auth->a_mutual_user);
541255570Strasz		login_compute_md5(id, auth->a_mutual_secret, challenge,
542255570Strasz		    challenge_len, response_bin, sizeof(response_bin));
543255570Strasz
544255570Strasz		chap_r = login_bin2hex(response_bin,
545255570Strasz		    sizeof(response_bin));
546255570Strasz		response_keys = keys_new();
547255570Strasz		keys_add(response_keys, "CHAP_N", auth->a_mutual_user);
548255570Strasz		keys_add(response_keys, "CHAP_R", chap_r);
549255570Strasz		free(chap_r);
550255570Strasz		keys_save(response_keys, response);
551255570Strasz		keys_delete(response_keys);
552255570Strasz	} else {
553255570Strasz		log_debugx("initiator did not request target authentication");
554255570Strasz	}
555255570Strasz
556255570Strasz	keys_delete(request_keys);
557255570Strasz	pdu_send(response);
558256192Strasz	pdu_delete(response);
559255570Strasz}
560255570Strasz
561255570Straszstatic void
562255570Straszlogin_chap(struct connection *conn, struct auth_group *ag)
563255570Strasz{
564255570Strasz	const struct auth *auth;
565255570Strasz	struct pdu *request;
566255570Strasz	char challenge_bin[LOGIN_CHALLENGE_LEN];
567255570Strasz	unsigned char id;
568255570Strasz	int rv;
569255570Strasz
570255570Strasz	/*
571255570Strasz	 * Receive CHAP_A PDU.
572255570Strasz	 */
573255570Strasz	log_debugx("beginning CHAP authentication; waiting for CHAP_A");
574255570Strasz	request = login_receive_chap_a(conn);
575255570Strasz
576255570Strasz	/*
577255570Strasz	 * Generate the challenge.
578255570Strasz	 */
579255570Strasz	rv = RAND_bytes(challenge_bin, sizeof(challenge_bin));
580255570Strasz	if (rv != 1) {
581255570Strasz		login_send_error(request, 0x03, 0x02);
582255570Strasz		log_errx(1, "RAND_bytes failed: %s",
583255570Strasz		    ERR_error_string(ERR_get_error(), NULL));
584255570Strasz	}
585255570Strasz	rv = RAND_bytes(&id, sizeof(id));
586255570Strasz	if (rv != 1) {
587255570Strasz		login_send_error(request, 0x03, 0x02);
588255570Strasz		log_errx(1, "RAND_bytes failed: %s",
589255570Strasz		    ERR_error_string(ERR_get_error(), NULL));
590255570Strasz	}
591255570Strasz
592255570Strasz	/*
593255570Strasz	 * Send the challenge.
594255570Strasz	 */
595255570Strasz	log_debugx("sending CHAP_C, binary challenge size is %zd bytes",
596255570Strasz	    sizeof(challenge_bin));
597255570Strasz	login_send_chap_c(request, id, challenge_bin,
598255570Strasz	    sizeof(challenge_bin));
599255570Strasz	pdu_delete(request);
600255570Strasz
601255570Strasz	/*
602255570Strasz	 * Receive CHAP_N/CHAP_R PDU and authenticate.
603255570Strasz	 */
604255570Strasz	log_debugx("waiting for CHAP_N/CHAP_R");
605255570Strasz	request = login_receive_chap_r(conn, ag, id, challenge_bin,
606255570Strasz	    sizeof(challenge_bin), &auth);
607255570Strasz
608255570Strasz	/*
609255570Strasz	 * Yay, authentication succeeded!
610255570Strasz	 */
611255570Strasz	log_debugx("authentication succeeded for user \"%s\"; "
612255570Strasz	    "transitioning to Negotiation Phase", auth->a_user);
613255570Strasz	login_send_chap_success(request, auth);
614255570Strasz	pdu_delete(request);
615255570Strasz}
616255570Strasz
617255570Straszstatic void
618255570Straszlogin_negotiate_key(struct pdu *request, const char *name,
619255570Strasz    const char *value, bool skipped_security, struct keys *response_keys)
620255570Strasz{
621255570Strasz	int which, tmp;
622255570Strasz	struct connection *conn;
623255570Strasz
624255570Strasz	conn = request->pdu_connection;
625255570Strasz
626255570Strasz	if (strcmp(name, "InitiatorName") == 0) {
627255570Strasz		if (!skipped_security)
628255570Strasz			log_errx(1, "initiator resent InitiatorName");
629255570Strasz	} else if (strcmp(name, "SessionType") == 0) {
630255570Strasz		if (!skipped_security)
631255570Strasz			log_errx(1, "initiator resent SessionType");
632255570Strasz	} else if (strcmp(name, "TargetName") == 0) {
633255570Strasz		if (!skipped_security)
634255570Strasz			log_errx(1, "initiator resent TargetName");
635255570Strasz	} else if (strcmp(name, "InitiatorAlias") == 0) {
636255570Strasz		if (conn->conn_initiator_alias != NULL)
637255570Strasz			free(conn->conn_initiator_alias);
638255570Strasz		conn->conn_initiator_alias = checked_strdup(value);
639255570Strasz	} else if (strcmp(value, "Irrelevant") == 0) {
640255570Strasz		/* Ignore. */
641255570Strasz	} else if (strcmp(name, "HeaderDigest") == 0) {
642255570Strasz		/*
643255570Strasz		 * We don't handle digests for discovery sessions.
644255570Strasz		 */
645255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
646255570Strasz			log_debugx("discovery session; digests disabled");
647255570Strasz			keys_add(response_keys, name, "None");
648255570Strasz			return;
649255570Strasz		}
650255570Strasz
651255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
652255570Strasz		switch (which) {
653255570Strasz		case 1:
654255570Strasz			log_debugx("initiator prefers CRC32C "
655255570Strasz			    "for header digest; we'll use it");
656255570Strasz			conn->conn_header_digest = CONN_DIGEST_CRC32C;
657255570Strasz			keys_add(response_keys, name, "CRC32C");
658255570Strasz			break;
659255570Strasz		case 2:
660255570Strasz			log_debugx("initiator prefers not to do "
661255570Strasz			    "header digest; we'll comply");
662255570Strasz			keys_add(response_keys, name, "None");
663255570Strasz			break;
664255570Strasz		default:
665255570Strasz			log_warnx("initiator sent unrecognized "
666255570Strasz			    "HeaderDigest value \"%s\"; will use None", value);
667255570Strasz			keys_add(response_keys, name, "None");
668255570Strasz			break;
669255570Strasz		}
670255570Strasz	} else if (strcmp(name, "DataDigest") == 0) {
671255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
672255570Strasz			log_debugx("discovery session; digests disabled");
673255570Strasz			keys_add(response_keys, name, "None");
674255570Strasz			return;
675255570Strasz		}
676255570Strasz
677255570Strasz		which = login_list_prefers(value, "CRC32C", "None");
678255570Strasz		switch (which) {
679255570Strasz		case 1:
680255570Strasz			log_debugx("initiator prefers CRC32C "
681255570Strasz			    "for data digest; we'll use it");
682255570Strasz			conn->conn_data_digest = CONN_DIGEST_CRC32C;
683255570Strasz			keys_add(response_keys, name, "CRC32C");
684255570Strasz			break;
685255570Strasz		case 2:
686255570Strasz			log_debugx("initiator prefers not to do "
687255570Strasz			    "data digest; we'll comply");
688255570Strasz			keys_add(response_keys, name, "None");
689255570Strasz			break;
690255570Strasz		default:
691255570Strasz			log_warnx("initiator sent unrecognized "
692255570Strasz			    "DataDigest value \"%s\"; will use None", value);
693255570Strasz			keys_add(response_keys, name, "None");
694255570Strasz			break;
695255570Strasz		}
696255570Strasz	} else if (strcmp(name, "MaxConnections") == 0) {
697255570Strasz		keys_add(response_keys, name, "1");
698255570Strasz	} else if (strcmp(name, "InitialR2T") == 0) {
699255570Strasz		keys_add(response_keys, name, "Yes");
700255570Strasz	} else if (strcmp(name, "ImmediateData") == 0) {
701255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
702255570Strasz			log_debugx("discovery session; ImmediateData irrelevant");
703255570Strasz			keys_add(response_keys, name, "Irrelevant");
704255570Strasz		} else {
705255570Strasz			if (strcmp(value, "Yes") == 0) {
706255570Strasz				conn->conn_immediate_data = true;
707255570Strasz				keys_add(response_keys, name, "Yes");
708255570Strasz			} else {
709255570Strasz				conn->conn_immediate_data = false;
710255570Strasz				keys_add(response_keys, name, "No");
711255570Strasz			}
712255570Strasz		}
713255570Strasz	} else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
714255570Strasz		tmp = strtoul(value, NULL, 10);
715255570Strasz		if (tmp <= 0) {
716255570Strasz			login_send_error(request, 0x02, 0x00);
717255570Strasz			log_errx(1, "received invalid "
718255570Strasz			    "MaxRecvDataSegmentLength");
719255570Strasz		}
720255570Strasz		if (tmp > MAX_DATA_SEGMENT_LENGTH) {
721255570Strasz			log_debugx("capping MaxDataSegmentLength from %d to %d",
722255570Strasz			    tmp, MAX_DATA_SEGMENT_LENGTH);
723255570Strasz			tmp = MAX_DATA_SEGMENT_LENGTH;
724255570Strasz		}
725255570Strasz		conn->conn_max_data_segment_length = tmp;
726255570Strasz		keys_add_int(response_keys, name, tmp);
727255570Strasz	} else if (strcmp(name, "MaxBurstLength") == 0) {
728255570Strasz		tmp = strtoul(value, NULL, 10);
729255570Strasz		if (tmp <= 0) {
730255570Strasz			login_send_error(request, 0x02, 0x00);
731255570Strasz			log_errx(1, "received invalid MaxBurstLength");
732255570Strasz		}
733255570Strasz		if (tmp > MAX_BURST_LENGTH) {
734255570Strasz			log_debugx("capping MaxBurstLength from %d to %d",
735255570Strasz			    tmp, MAX_BURST_LENGTH);
736255570Strasz			tmp = MAX_BURST_LENGTH;
737255570Strasz		}
738255570Strasz		conn->conn_max_burst_length = tmp;
739255570Strasz		keys_add(response_keys, name, value);
740255570Strasz	} else if (strcmp(name, "FirstBurstLength") == 0) {
741255570Strasz		tmp = strtoul(value, NULL, 10);
742255570Strasz		if (tmp <= 0) {
743255570Strasz			login_send_error(request, 0x02, 0x00);
744255570Strasz			log_errx(1, "received invalid "
745255570Strasz			    "FirstBurstLength");
746255570Strasz		}
747255570Strasz		if (tmp > MAX_DATA_SEGMENT_LENGTH) {
748255570Strasz			log_debugx("capping FirstBurstLength from %d to %d",
749255570Strasz			    tmp, MAX_DATA_SEGMENT_LENGTH);
750255570Strasz			tmp = MAX_DATA_SEGMENT_LENGTH;
751255570Strasz		}
752255570Strasz		/*
753255570Strasz		 * We don't pass the value to the kernel; it only enforces
754255570Strasz		 * hardcoded limit anyway.
755255570Strasz		 */
756255570Strasz		keys_add_int(response_keys, name, tmp);
757255570Strasz	} else if (strcmp(name, "DefaultTime2Wait") == 0) {
758255570Strasz		keys_add(response_keys, name, value);
759255570Strasz	} else if (strcmp(name, "DefaultTime2Retain") == 0) {
760255570Strasz		keys_add(response_keys, name, "0");
761255570Strasz	} else if (strcmp(name, "MaxOutstandingR2T") == 0) {
762255570Strasz		keys_add(response_keys, name, "1");
763255570Strasz	} else if (strcmp(name, "DataPDUInOrder") == 0) {
764255570Strasz		keys_add(response_keys, name, "Yes");
765255570Strasz	} else if (strcmp(name, "DataSequenceInOrder") == 0) {
766255570Strasz		keys_add(response_keys, name, "Yes");
767255570Strasz	} else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
768255570Strasz		keys_add(response_keys, name, "0");
769255570Strasz	} else if (strcmp(name, "OFMarker") == 0) {
770255570Strasz		keys_add(response_keys, name, "No");
771255570Strasz	} else if (strcmp(name, "IFMarker") == 0) {
772255570Strasz		keys_add(response_keys, name, "No");
773255570Strasz	} else {
774255570Strasz		log_debugx("unknown key \"%s\"; responding "
775255570Strasz		    "with NotUnderstood", name);
776255570Strasz		keys_add(response_keys, name, "NotUnderstood");
777255570Strasz	}
778255570Strasz}
779255570Strasz
780255570Straszstatic void
781255570Straszlogin_negotiate(struct connection *conn, struct pdu *request)
782255570Strasz{
783255570Strasz	struct pdu *response;
784255570Strasz	struct iscsi_bhs_login_response *bhslr2;
785255570Strasz	struct keys *request_keys, *response_keys;
786255570Strasz	int i;
787255570Strasz	bool skipped_security;
788255570Strasz
789255570Strasz	if (request == NULL) {
790265515Strasz		log_debugx("beginning operational parameter negotiation; "
791255570Strasz		    "waiting for Login PDU");
792255570Strasz		request = login_receive(conn, false);
793255570Strasz		skipped_security = false;
794255570Strasz	} else
795255570Strasz		skipped_security = true;
796255570Strasz
797255570Strasz	request_keys = keys_new();
798255570Strasz	keys_load(request_keys, request);
799255570Strasz
800255570Strasz	response = login_new_response(request);
801255570Strasz	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
802255570Strasz	bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
803255570Strasz	bhslr2->bhslr_tsih = htons(0xbadd);
804255570Strasz	login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
805255570Strasz	login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE);
806255570Strasz	response_keys = keys_new();
807255570Strasz	for (i = 0; i < KEYS_MAX; i++) {
808255570Strasz		if (request_keys->keys_names[i] == NULL)
809255570Strasz			break;
810255570Strasz
811255570Strasz		login_negotiate_key(request, request_keys->keys_names[i],
812255570Strasz		    request_keys->keys_values[i], skipped_security,
813255570Strasz		    response_keys);
814255570Strasz	}
815255570Strasz
816265515Strasz	log_debugx("operational parameter negotiation done; "
817255570Strasz	    "transitioning to Full Feature Phase");
818255570Strasz
819255570Strasz	keys_save(response_keys, response);
820255570Strasz	pdu_send(response);
821255570Strasz	pdu_delete(response);
822255570Strasz	keys_delete(response_keys);
823255570Strasz	pdu_delete(request);
824255570Strasz	keys_delete(request_keys);
825255570Strasz}
826255570Strasz
827255570Straszvoid
828255570Straszlogin(struct connection *conn)
829255570Strasz{
830255570Strasz	struct pdu *request, *response;
831255570Strasz	struct iscsi_bhs_login_request *bhslr;
832255570Strasz	struct iscsi_bhs_login_response *bhslr2;
833255570Strasz	struct keys *request_keys, *response_keys;
834255570Strasz	struct auth_group *ag;
835255570Strasz	const char *initiator_name, *initiator_alias, *session_type,
836255570Strasz	    *target_name, *auth_method;
837255570Strasz	char *portal_group_tag;
838255570Strasz	int rv;
839255570Strasz
840255570Strasz	/*
841255570Strasz	 * Handle the initial Login Request - figure out required authentication
842255570Strasz	 * method and either transition to the next phase, if no authentication
843255570Strasz	 * is required, or call appropriate authentication code.
844255570Strasz	 */
845255570Strasz	log_debugx("beginning Login Phase; waiting for Login PDU");
846255570Strasz	request = login_receive(conn, true);
847255570Strasz	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
848255570Strasz	if (bhslr->bhslr_tsih != 0) {
849255570Strasz		login_send_error(request, 0x02, 0x0a);
850255570Strasz		log_errx(1, "received Login PDU with non-zero TSIH");
851255570Strasz	}
852255570Strasz
853268684Smav	memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid,
854268684Smav	    sizeof(conn->conn_initiator_isid));
855268684Smav
856255570Strasz	/*
857255570Strasz	 * XXX: Implement the C flag some day.
858255570Strasz	 */
859255570Strasz	request_keys = keys_new();
860255570Strasz	keys_load(request_keys, request);
861255570Strasz
862255570Strasz	assert(conn->conn_initiator_name == NULL);
863255570Strasz	initiator_name = keys_find(request_keys, "InitiatorName");
864255570Strasz	if (initiator_name == NULL) {
865255570Strasz		login_send_error(request, 0x02, 0x07);
866255570Strasz		log_errx(1, "received Login PDU without InitiatorName");
867255570Strasz	}
868255570Strasz	if (valid_iscsi_name(initiator_name) == false) {
869255570Strasz		login_send_error(request, 0x02, 0x00);
870255570Strasz		log_errx(1, "received Login PDU with invalid InitiatorName");
871255570Strasz	}
872255570Strasz	conn->conn_initiator_name = checked_strdup(initiator_name);
873255570Strasz	log_set_peer_name(conn->conn_initiator_name);
874255570Strasz	/*
875255570Strasz	 * XXX: This doesn't work (does nothing) because of Capsicum.
876255570Strasz	 */
877255570Strasz	setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name);
878255570Strasz
879255570Strasz	initiator_alias = keys_find(request_keys, "InitiatorAlias");
880255570Strasz	if (initiator_alias != NULL)
881255570Strasz		conn->conn_initiator_alias = checked_strdup(initiator_alias);
882255570Strasz
883255570Strasz	assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE);
884255570Strasz	session_type = keys_find(request_keys, "SessionType");
885255570Strasz	if (session_type != NULL) {
886255570Strasz		if (strcmp(session_type, "Normal") == 0) {
887255570Strasz			conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
888255570Strasz		} else if (strcmp(session_type, "Discovery") == 0) {
889255570Strasz			conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY;
890255570Strasz		} else {
891255570Strasz			login_send_error(request, 0x02, 0x00);
892255570Strasz			log_errx(1, "received Login PDU with invalid "
893255570Strasz			    "SessionType \"%s\"", session_type);
894255570Strasz		}
895255570Strasz	} else
896255570Strasz		conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
897255570Strasz
898255570Strasz	assert(conn->conn_target == NULL);
899255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
900255570Strasz		target_name = keys_find(request_keys, "TargetName");
901255570Strasz		if (target_name == NULL) {
902255570Strasz			login_send_error(request, 0x02, 0x07);
903255570Strasz			log_errx(1, "received Login PDU without TargetName");
904255570Strasz		}
905255570Strasz
906255570Strasz		conn->conn_target =
907255570Strasz		    target_find(conn->conn_portal->p_portal_group->pg_conf,
908255570Strasz		    target_name);
909255570Strasz		if (conn->conn_target == NULL) {
910255570Strasz			login_send_error(request, 0x02, 0x03);
911255570Strasz			log_errx(1, "requested target \"%s\" not found",
912255570Strasz			    target_name);
913255570Strasz		}
914255570Strasz	}
915255570Strasz
916255570Strasz	/*
917255570Strasz	 * At this point we know what kind of authentication we need.
918255570Strasz	 */
919255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
920255570Strasz		ag = conn->conn_target->t_auth_group;
921255570Strasz		if (ag->ag_name != NULL) {
922255570Strasz			log_debugx("initiator requests to connect "
923255570Strasz			    "to target \"%s\"; auth-group \"%s\"",
924263723Strasz			    conn->conn_target->t_name,
925255570Strasz			    conn->conn_target->t_auth_group->ag_name);
926255570Strasz		} else {
927255570Strasz			log_debugx("initiator requests to connect "
928263723Strasz			    "to target \"%s\"", conn->conn_target->t_name);
929255570Strasz		}
930255570Strasz	} else {
931255570Strasz		assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
932255570Strasz		ag = conn->conn_portal->p_portal_group->pg_discovery_auth_group;
933255570Strasz		if (ag->ag_name != NULL) {
934255570Strasz			log_debugx("initiator requests "
935255570Strasz			    "discovery session; auth-group \"%s\"", ag->ag_name);
936255570Strasz		} else {
937255570Strasz			log_debugx("initiator requests discovery session");
938255570Strasz		}
939255570Strasz	}
940255570Strasz
941255570Strasz	/*
942263720Strasz	 * Enforce initiator-name and initiator-portal.
943263720Strasz	 */
944263720Strasz	if (auth_name_defined(ag)) {
945263720Strasz		if (auth_name_find(ag, initiator_name) == NULL) {
946263720Strasz			login_send_error(request, 0x02, 0x02);
947263720Strasz			log_errx(1, "initiator does not match allowed "
948263720Strasz			    "initiator names");
949263720Strasz		}
950263720Strasz		log_debugx("initiator matches allowed initiator names");
951263720Strasz	} else {
952263720Strasz		log_debugx("auth-group does not define initiator name "
953263720Strasz		    "restrictions");
954263720Strasz	}
955263720Strasz
956263720Strasz	if (auth_portal_defined(ag)) {
957270137Smav		if (auth_portal_find(ag, &conn->conn_initiator_sa) == NULL) {
958263720Strasz			login_send_error(request, 0x02, 0x02);
959263720Strasz			log_errx(1, "initiator does not match allowed "
960263720Strasz			    "initiator portals");
961263720Strasz		}
962263720Strasz		log_debugx("initiator matches allowed initiator portals");
963263720Strasz	} else {
964263720Strasz		log_debugx("auth-group does not define initiator portal "
965263720Strasz		    "restrictions");
966263720Strasz	}
967263720Strasz
968263720Strasz	/*
969255570Strasz	 * Let's see if the initiator intends to do any kind of authentication
970255570Strasz	 * at all.
971255570Strasz	 */
972255570Strasz	if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
973255570Strasz		if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
974255570Strasz			login_send_error(request, 0x02, 0x01);
975255570Strasz			log_errx(1, "initiator skipped the authentication, "
976255570Strasz			    "but authentication is required");
977255570Strasz		}
978255570Strasz
979255570Strasz		keys_delete(request_keys);
980255570Strasz
981255570Strasz		log_debugx("initiator skipped the authentication, "
982255570Strasz		    "and we don't need it; proceeding with negotiation");
983255570Strasz		login_negotiate(conn, request);
984255570Strasz		return;
985255570Strasz	}
986255570Strasz
987255570Strasz	if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) {
988255570Strasz		/*
989255570Strasz		 * Initiator might want to to authenticate,
990255570Strasz		 * but we don't need it.
991255570Strasz		 */
992255570Strasz		log_debugx("authentication not required; "
993265515Strasz		    "transitioning to operational parameter negotiation");
994255570Strasz
995255570Strasz		if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0)
996255570Strasz			log_warnx("initiator did not set the \"T\" flag; "
997255570Strasz			    "transitioning anyway");
998255570Strasz
999255570Strasz		response = login_new_response(request);
1000255570Strasz		bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
1001255570Strasz		bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
1002255570Strasz		login_set_nsg(response,
1003255570Strasz		    BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
1004255570Strasz		response_keys = keys_new();
1005255570Strasz		/*
1006255570Strasz		 * Required by Linux initiator.
1007255570Strasz		 */
1008255570Strasz		auth_method = keys_find(request_keys, "AuthMethod");
1009255570Strasz		if (auth_method != NULL &&
1010255570Strasz		    login_list_contains(auth_method, "None"))
1011255570Strasz			keys_add(response_keys, "AuthMethod", "None");
1012255570Strasz
1013255570Strasz		if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
1014255570Strasz			if (conn->conn_target->t_alias != NULL)
1015255570Strasz				keys_add(response_keys,
1016255570Strasz				    "TargetAlias", conn->conn_target->t_alias);
1017255570Strasz			rv = asprintf(&portal_group_tag, "%d",
1018255570Strasz			    conn->conn_portal->p_portal_group->pg_tag);
1019255570Strasz			if (rv <= 0)
1020255570Strasz				log_err(1, "asprintf");
1021255570Strasz			keys_add(response_keys,
1022255570Strasz			    "TargetPortalGroupTag", portal_group_tag);
1023255570Strasz			free(portal_group_tag);
1024255570Strasz		}
1025255570Strasz		keys_save(response_keys, response);
1026255570Strasz		pdu_send(response);
1027255570Strasz		pdu_delete(response);
1028255570Strasz		keys_delete(response_keys);
1029255570Strasz		pdu_delete(request);
1030255570Strasz		keys_delete(request_keys);
1031255570Strasz
1032255570Strasz		login_negotiate(conn, NULL);
1033255570Strasz		return;
1034255570Strasz	}
1035255570Strasz
1036263729Strasz	if (ag->ag_type == AG_TYPE_DENY) {
1037263729Strasz		login_send_error(request, 0x02, 0x01);
1038263734Strasz		log_errx(1, "auth-type is \"deny\"");
1039263729Strasz	}
1040263729Strasz
1041259305Strasz	if (ag->ag_type == AG_TYPE_UNKNOWN) {
1042259305Strasz		/*
1043259305Strasz		 * This can happen with empty auth-group.
1044259305Strasz		 */
1045259305Strasz		login_send_error(request, 0x02, 0x01);
1046263734Strasz		log_errx(1, "auth-type not set, denying access");
1047259305Strasz	}
1048259305Strasz
1049255570Strasz	log_debugx("CHAP authentication required");
1050255570Strasz
1051255570Strasz	auth_method = keys_find(request_keys, "AuthMethod");
1052255570Strasz	if (auth_method == NULL) {
1053255570Strasz		login_send_error(request, 0x02, 0x07);
1054255570Strasz		log_errx(1, "received Login PDU without AuthMethod");
1055255570Strasz	}
1056255570Strasz	/*
1057255570Strasz	 * XXX: This should be Reject, not just a login failure (5.3.2).
1058255570Strasz	 */
1059255570Strasz	if (login_list_contains(auth_method, "CHAP") == 0) {
1060255570Strasz		login_send_error(request, 0x02, 0x01);
1061255570Strasz		log_errx(1, "initiator requests unsupported AuthMethod \"%s\" "
1062255570Strasz		    "instead of \"CHAP\"", auth_method);
1063255570Strasz	}
1064255570Strasz
1065255570Strasz	response = login_new_response(request);
1066255570Strasz
1067255570Strasz	response_keys = keys_new();
1068255570Strasz	keys_add(response_keys, "AuthMethod", "CHAP");
1069255570Strasz	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
1070255570Strasz		rv = asprintf(&portal_group_tag, "%d",
1071255570Strasz		    conn->conn_portal->p_portal_group->pg_tag);
1072255570Strasz		if (rv <= 0)
1073255570Strasz			log_err(1, "asprintf");
1074255570Strasz		keys_add(response_keys,
1075255570Strasz		    "TargetPortalGroupTag", portal_group_tag);
1076255570Strasz		free(portal_group_tag);
1077255570Strasz		if (conn->conn_target->t_alias != NULL)
1078255570Strasz			keys_add(response_keys,
1079255570Strasz			    "TargetAlias", conn->conn_target->t_alias);
1080255570Strasz	}
1081255570Strasz	keys_save(response_keys, response);
1082255570Strasz
1083255570Strasz	pdu_send(response);
1084255570Strasz	pdu_delete(response);
1085255570Strasz	keys_delete(response_keys);
1086255570Strasz	pdu_delete(request);
1087255570Strasz	keys_delete(request_keys);
1088255570Strasz
1089255570Strasz	login_chap(conn, ag);
1090255570Strasz
1091255570Strasz	login_negotiate(conn, NULL);
1092255570Strasz}
1093