login.c revision 268703
1/*-
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: stable/10/usr.sbin/iscsid/login.c 268703 2014-07-15 18:21:26Z mav $
30 */
31
32#include <sys/types.h>
33#include <assert.h>
34#include <stdbool.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <netinet/in.h>
39#include <openssl/err.h>
40#include <openssl/md5.h>
41#include <openssl/rand.h>
42
43#include "iscsid.h"
44#include "iscsi_proto.h"
45
46static int
47login_nsg(const struct pdu *response)
48{
49	struct iscsi_bhs_login_response *bhslr;
50
51	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
52
53	return (bhslr->bhslr_flags & 0x03);
54}
55
56static void
57login_set_nsg(struct pdu *request, int nsg)
58{
59	struct iscsi_bhs_login_request *bhslr;
60
61	assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
62	    nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
63	    nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
64
65	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
66
67	bhslr->bhslr_flags &= 0xFC;
68	bhslr->bhslr_flags |= nsg;
69}
70
71static void
72login_set_csg(struct pdu *request, int csg)
73{
74	struct iscsi_bhs_login_request *bhslr;
75
76	assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
77	    csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
78	    csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
79
80	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
81
82	bhslr->bhslr_flags &= 0xF3;
83	bhslr->bhslr_flags |= csg << 2;
84}
85
86static const char *
87login_target_error_str(int class, int detail)
88{
89	static char msg[128];
90
91	/*
92	 * RFC 3270, 10.13.5.  Status-Class and Status-Detail
93	 */
94	switch (class) {
95	case 0x01:
96		switch (detail) {
97		case 0x01:
98			return ("Target moved temporarily");
99		case 0x02:
100			return ("Target moved permanently");
101		default:
102			snprintf(msg, sizeof(msg), "unknown redirection; "
103			    "Status-Class 0x%x, Status-Detail 0x%x",
104			    class, detail);
105			return (msg);
106		}
107	case 0x02:
108		switch (detail) {
109		case 0x00:
110			return ("Initiator error");
111		case 0x01:
112			return ("Authentication failure");
113		case 0x02:
114			return ("Authorization failure");
115		case 0x03:
116			return ("Not found");
117		case 0x04:
118			return ("Target removed");
119		case 0x05:
120			return ("Unsupported version");
121		case 0x06:
122			return ("Too many connections");
123		case 0x07:
124			return ("Missing parameter");
125		case 0x08:
126			return ("Can't include in session");
127		case 0x09:
128			return ("Session type not supported");
129		case 0x0a:
130			return ("Session does not exist");
131		case 0x0b:
132			return ("Invalid during login");
133		default:
134			snprintf(msg, sizeof(msg), "unknown initiator error; "
135			    "Status-Class 0x%x, Status-Detail 0x%x",
136			    class, detail);
137			return (msg);
138		}
139	case 0x03:
140		switch (detail) {
141		case 0x00:
142			return ("Target error");
143		case 0x01:
144			return ("Service unavailable");
145		case 0x02:
146			return ("Out of resources");
147		default:
148			snprintf(msg, sizeof(msg), "unknown target error; "
149			    "Status-Class 0x%x, Status-Detail 0x%x",
150			    class, detail);
151			return (msg);
152		}
153	default:
154		snprintf(msg, sizeof(msg), "unknown error; "
155		    "Status-Class 0x%x, Status-Detail 0x%x",
156		    class, detail);
157		return (msg);
158	}
159}
160
161static struct pdu *
162login_receive(struct connection *conn, bool initial)
163{
164	struct pdu *response;
165	struct iscsi_bhs_login_response *bhslr;
166	const char *errorstr;
167
168	response = pdu_new(conn);
169	pdu_receive(response);
170	if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) {
171		log_errx(1, "protocol error: received invalid opcode 0x%x",
172		    response->pdu_bhs->bhs_opcode);
173	}
174	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
175	/*
176	 * XXX: Implement the C flag some day.
177	 */
178	if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0)
179		log_errx(1, "received Login PDU with unsupported \"C\" flag");
180	if (bhslr->bhslr_version_max != 0x00)
181		log_errx(1, "received Login PDU with unsupported "
182		    "Version-max 0x%x", bhslr->bhslr_version_max);
183	if (bhslr->bhslr_version_active != 0x00)
184		log_errx(1, "received Login PDU with unsupported "
185		    "Version-active 0x%x", bhslr->bhslr_version_active);
186	if (bhslr->bhslr_status_class != 0) {
187		errorstr = login_target_error_str(bhslr->bhslr_status_class,
188		    bhslr->bhslr_status_detail);
189		fail(conn, errorstr);
190		log_errx(1, "target returned error: %s", errorstr);
191	}
192	if (initial == false &&
193	    ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
194		/*
195		 * It's a warning, not an error, to work around what seems
196		 * to be bug in NetBSD iSCSI target.
197		 */
198		log_warnx("received Login PDU with wrong StatSN: "
199		    "is %d, should be %d", ntohl(bhslr->bhslr_statsn),
200		    conn->conn_statsn + 1);
201	}
202	conn->conn_tsih = ntohs(bhslr->bhslr_tsih);
203	conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
204
205	return (response);
206}
207
208static struct pdu *
209login_new_request(struct connection *conn)
210{
211	struct pdu *request;
212	struct iscsi_bhs_login_request *bhslr;
213
214	request = pdu_new(conn);
215	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
216	bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST |
217	    ISCSI_BHS_OPCODE_IMMEDIATE;
218	bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT;
219	login_set_csg(request, BHSLR_STAGE_SECURITY_NEGOTIATION);
220	login_set_nsg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
221	memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid));
222	bhslr->bhslr_tsih = htons(conn->conn_tsih);
223	bhslr->bhslr_initiator_task_tag = 0;
224	bhslr->bhslr_cmdsn = 0;
225	bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
226
227	return (request);
228}
229
230static int
231login_list_prefers(const char *list,
232    const char *choice1, const char *choice2)
233{
234	char *tofree, *str, *token;
235
236	tofree = str = checked_strdup(list);
237
238	while ((token = strsep(&str, ",")) != NULL) {
239		if (strcmp(token, choice1) == 0) {
240			free(tofree);
241			return (1);
242		}
243		if (strcmp(token, choice2) == 0) {
244			free(tofree);
245			return (2);
246		}
247	}
248	free(tofree);
249	return (-1);
250}
251
252static int
253login_hex2int(const char hex)
254{
255	switch (hex) {
256	case '0':
257		return (0x00);
258	case '1':
259		return (0x01);
260	case '2':
261		return (0x02);
262	case '3':
263		return (0x03);
264	case '4':
265		return (0x04);
266	case '5':
267		return (0x05);
268	case '6':
269		return (0x06);
270	case '7':
271		return (0x07);
272	case '8':
273		return (0x08);
274	case '9':
275		return (0x09);
276	case 'a':
277	case 'A':
278		return (0x0a);
279	case 'b':
280	case 'B':
281		return (0x0b);
282	case 'c':
283	case 'C':
284		return (0x0c);
285	case 'd':
286	case 'D':
287		return (0x0d);
288	case 'e':
289	case 'E':
290		return (0x0e);
291	case 'f':
292	case 'F':
293		return (0x0f);
294	default:
295		return (-1);
296	}
297}
298
299/*
300 * XXX: Review this _carefully_.
301 */
302static int
303login_hex2bin(const char *hex, char **binp, size_t *bin_lenp)
304{
305	int i, hex_len, nibble;
306	bool lo = true; /* As opposed to 'hi'. */
307	char *bin;
308	size_t bin_off, bin_len;
309
310	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
311		log_warnx("malformed variable, should start with \"0x\"");
312		return (-1);
313	}
314
315	hex += strlen("0x");
316	hex_len = strlen(hex);
317	if (hex_len < 1) {
318		log_warnx("malformed variable; doesn't contain anything "
319		    "but \"0x\"");
320		return (-1);
321	}
322
323	bin_len = hex_len / 2 + hex_len % 2;
324	bin = calloc(bin_len, 1);
325	if (bin == NULL)
326		log_err(1, "calloc");
327
328	bin_off = bin_len - 1;
329	for (i = hex_len - 1; i >= 0; i--) {
330		nibble = login_hex2int(hex[i]);
331		if (nibble < 0) {
332			log_warnx("malformed variable, invalid char \"%c\"",
333			    hex[i]);
334			return (-1);
335		}
336
337		assert(bin_off < bin_len);
338		if (lo) {
339			bin[bin_off] = nibble;
340			lo = false;
341		} else {
342			bin[bin_off] |= nibble << 4;
343			bin_off--;
344			lo = true;
345		}
346	}
347
348	*binp = bin;
349	*bin_lenp = bin_len;
350	return (0);
351}
352
353static char *
354login_bin2hex(const char *bin, size_t bin_len)
355{
356	unsigned char *hex, *tmp, ch;
357	size_t hex_len;
358	size_t i;
359
360	hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
361	hex = malloc(hex_len);
362	if (hex == NULL)
363		log_err(1, "malloc");
364
365	tmp = hex;
366	tmp += sprintf(tmp, "0x");
367	for (i = 0; i < bin_len; i++) {
368		ch = bin[i];
369		tmp += sprintf(tmp, "%02x", ch);
370	}
371
372	return (hex);
373}
374
375static void
376login_compute_md5(const char id, const char *secret,
377    const void *challenge, size_t challenge_len, void *response,
378    size_t response_len)
379{
380	MD5_CTX ctx;
381	int rv;
382
383	assert(response_len == MD5_DIGEST_LENGTH);
384
385	MD5_Init(&ctx);
386	MD5_Update(&ctx, &id, sizeof(id));
387	MD5_Update(&ctx, secret, strlen(secret));
388	MD5_Update(&ctx, challenge, challenge_len);
389	rv = MD5_Final(response, &ctx);
390	if (rv != 1)
391		log_errx(1, "MD5_Final");
392}
393
394static void
395login_negotiate_key(struct connection *conn, const char *name,
396    const char *value)
397{
398	int which, tmp;
399
400	if (strcmp(name, "TargetAlias") == 0) {
401		strlcpy(conn->conn_target_alias, value,
402		    sizeof(conn->conn_target_alias));
403	} else if (strcmp(value, "Irrelevant") == 0) {
404		/* Ignore. */
405	} else if (strcmp(name, "HeaderDigest") == 0) {
406		which = login_list_prefers(value, "CRC32C", "None");
407		switch (which) {
408		case 1:
409			log_debugx("target prefers CRC32C "
410			    "for header digest; we'll use it");
411			conn->conn_header_digest = CONN_DIGEST_CRC32C;
412			break;
413		case 2:
414			log_debugx("target prefers not to do "
415			    "header digest; we'll comply");
416			break;
417		default:
418			log_warnx("target sent unrecognized "
419			    "HeaderDigest value \"%s\"; will use None", value);
420			break;
421		}
422	} else if (strcmp(name, "DataDigest") == 0) {
423		which = login_list_prefers(value, "CRC32C", "None");
424		switch (which) {
425		case 1:
426			log_debugx("target prefers CRC32C "
427			    "for data digest; we'll use it");
428			conn->conn_data_digest = CONN_DIGEST_CRC32C;
429			break;
430		case 2:
431			log_debugx("target prefers not to do "
432			    "data digest; we'll comply");
433			break;
434		default:
435			log_warnx("target sent unrecognized "
436			    "DataDigest value \"%s\"; will use None", value);
437			break;
438		}
439	} else if (strcmp(name, "MaxConnections") == 0) {
440		/* Ignore. */
441	} else if (strcmp(name, "InitialR2T") == 0) {
442		if (strcmp(value, "Yes") == 0)
443			conn->conn_initial_r2t = true;
444		else
445			conn->conn_initial_r2t = false;
446	} else if (strcmp(name, "ImmediateData") == 0) {
447		if (strcmp(value, "Yes") == 0)
448			conn->conn_immediate_data = true;
449		else
450			conn->conn_immediate_data = false;
451	} else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
452		tmp = strtoul(value, NULL, 10);
453		if (tmp <= 0)
454			log_errx(1, "received invalid "
455			    "MaxRecvDataSegmentLength");
456		conn->conn_max_data_segment_length = tmp;
457	} else if (strcmp(name, "MaxBurstLength") == 0) {
458		if (conn->conn_immediate_data) {
459			tmp = strtoul(value, NULL, 10);
460			if (tmp <= 0)
461				log_errx(1, "received invalid MaxBurstLength");
462			conn->conn_max_burst_length = tmp;
463		}
464	} else if (strcmp(name, "FirstBurstLength") == 0) {
465		tmp = strtoul(value, NULL, 10);
466		if (tmp <= 0)
467			log_errx(1, "received invalid FirstBurstLength");
468		conn->conn_first_burst_length = tmp;
469	} else if (strcmp(name, "DefaultTime2Wait") == 0) {
470		/* Ignore */
471	} else if (strcmp(name, "DefaultTime2Retain") == 0) {
472		/* Ignore */
473	} else if (strcmp(name, "MaxOutstandingR2T") == 0) {
474		/* Ignore */
475	} else if (strcmp(name, "DataPDUInOrder") == 0) {
476		/* Ignore */
477	} else if (strcmp(name, "DataSequenceInOrder") == 0) {
478		/* Ignore */
479	} else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
480		/* Ignore */
481	} else if (strcmp(name, "OFMarker") == 0) {
482		/* Ignore */
483	} else if (strcmp(name, "IFMarker") == 0) {
484		/* Ignore */
485	} else if (strcmp(name, "TargetPortalGroupTag") == 0) {
486		/* Ignore */
487	} else {
488		log_debugx("unknown key \"%s\"; ignoring",  name);
489	}
490}
491
492static void
493login_negotiate(struct connection *conn)
494{
495	struct pdu *request, *response;
496	struct keys *request_keys, *response_keys;
497	struct iscsi_bhs_login_response *bhslr;
498	int i;
499
500	log_debugx("beginning parameter negotiation");
501	request = login_new_request(conn);
502	login_set_csg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
503	login_set_nsg(request, BHSLR_STAGE_FULL_FEATURE_PHASE);
504	request_keys = keys_new();
505
506	/*
507	 * The following keys are irrelevant for discovery sessions.
508	 */
509	if (conn->conn_conf.isc_discovery == 0) {
510		if (conn->conn_conf.isc_header_digest != 0)
511			keys_add(request_keys, "HeaderDigest", "CRC32C");
512		else
513			keys_add(request_keys, "HeaderDigest", "None");
514		if (conn->conn_conf.isc_data_digest != 0)
515			keys_add(request_keys, "DataDigest", "CRC32C");
516		else
517			keys_add(request_keys, "DataDigest", "None");
518
519		keys_add(request_keys, "ImmediateData", "Yes");
520		keys_add_int(request_keys, "MaxBurstLength",
521		    ISCSI_MAX_DATA_SEGMENT_LENGTH);
522		keys_add_int(request_keys, "FirstBurstLength",
523		    ISCSI_MAX_DATA_SEGMENT_LENGTH);
524		keys_add(request_keys, "InitialR2T", "Yes");
525	} else {
526		keys_add(request_keys, "HeaderDigest", "None");
527		keys_add(request_keys, "DataDigest", "None");
528	}
529
530	keys_add_int(request_keys, "MaxRecvDataSegmentLength",
531	    ISCSI_MAX_DATA_SEGMENT_LENGTH);
532	keys_add(request_keys, "DefaultTime2Wait", "0");
533	keys_add(request_keys, "DefaultTime2Retain", "0");
534	keys_add(request_keys, "ErrorRecoveryLevel", "0");
535	keys_add(request_keys, "MaxOutstandingR2T", "1");
536	keys_save(request_keys, request);
537	keys_delete(request_keys);
538	request_keys = NULL;
539	pdu_send(request);
540	pdu_delete(request);
541	request = NULL;
542
543	response = login_receive(conn, false);
544	response_keys = keys_new();
545	keys_load(response_keys, response);
546	for (i = 0; i < KEYS_MAX; i++) {
547		if (response_keys->keys_names[i] == NULL)
548			break;
549
550		login_negotiate_key(conn,
551		    response_keys->keys_names[i], response_keys->keys_values[i]);
552	}
553
554	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
555	if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0)
556		log_warnx("received final login response "
557		    "without the \"T\" flag");
558	else if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE)
559		log_warnx("received final login response with wrong NSG 0x%x",
560		    login_nsg(response));
561
562	log_debugx("parameter negotiation done; "
563	    "transitioning to Full Feature phase");
564
565	keys_delete(response_keys);
566	pdu_delete(response);
567}
568
569static void
570login_send_chap_a(struct connection *conn)
571{
572	struct pdu *request;
573	struct keys *request_keys;
574
575	request = login_new_request(conn);
576	request_keys = keys_new();
577	keys_add(request_keys, "CHAP_A", "5");
578	keys_save(request_keys, request);
579	keys_delete(request_keys);
580	pdu_send(request);
581	pdu_delete(request);
582}
583
584static void
585login_send_chap_r(struct pdu *response)
586{
587	struct connection *conn;
588	struct pdu *request;
589	struct keys *request_keys, *response_keys;
590	const char *chap_a, *chap_c, *chap_i;
591	char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH];
592	size_t challenge_len;
593	int error, rv;
594	unsigned char id;
595        char *mutual_chap_c, mutual_chap_i[4];
596
597	/*
598	 * As in the rest of the initiator, 'request' means
599	 * 'initiator -> target', and 'response' means 'target -> initiator',
600	 *
601	 * So, here the 'response' from the target is the packet that contains
602	 * CHAP challenge; our CHAP response goes into 'request'.
603	 */
604
605	conn = response->pdu_connection;
606
607	response_keys = keys_new();
608	keys_load(response_keys, response);
609
610	/*
611	 * First, compute the response.
612	 */
613	chap_a = keys_find(response_keys, "CHAP_A");
614	if (chap_a == NULL)
615		log_errx(1, "received CHAP packet without CHAP_A");
616	chap_c = keys_find(response_keys, "CHAP_C");
617	if (chap_c == NULL)
618		log_errx(1, "received CHAP packet without CHAP_C");
619	chap_i = keys_find(response_keys, "CHAP_I");
620	if (chap_i == NULL)
621		log_errx(1, "received CHAP packet without CHAP_I");
622
623	if (strcmp(chap_a, "5") != 0)
624		log_errx(1, "received CHAP packet "
625		    "with unsupported CHAP_A \"%s\"", chap_a);
626	id = strtoul(chap_i, NULL, 10);
627	error = login_hex2bin(chap_c, &challenge, &challenge_len);
628	if (error != 0)
629		log_errx(1, "received CHAP packet with malformed CHAP_C");
630	login_compute_md5(id, conn->conn_conf.isc_secret,
631	    challenge, challenge_len, response_bin, sizeof(response_bin));
632	free(challenge);
633	chap_r = login_bin2hex(response_bin, sizeof(response_bin));
634
635	keys_delete(response_keys);
636
637	request = login_new_request(conn);
638	request_keys = keys_new();
639	keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user);
640	keys_add(request_keys, "CHAP_R", chap_r);
641	free(chap_r);
642
643	/*
644	 * If we want mutual authentication, we're expected to send
645	 * our CHAP_I/CHAP_C now.
646	 */
647	if (conn->conn_conf.isc_mutual_user[0] != '\0') {
648		log_debugx("requesting mutual authentication; "
649		    "binary challenge size is %zd bytes",
650		    sizeof(conn->conn_mutual_challenge));
651
652		rv = RAND_bytes(conn->conn_mutual_challenge,
653		    sizeof(conn->conn_mutual_challenge));
654		if (rv != 1) {
655			log_errx(1, "RAND_bytes failed: %s",
656			    ERR_error_string(ERR_get_error(), NULL));
657		}
658		rv = RAND_bytes(&conn->conn_mutual_id,
659		    sizeof(conn->conn_mutual_id));
660		if (rv != 1) {
661			log_errx(1, "RAND_bytes failed: %s",
662			    ERR_error_string(ERR_get_error(), NULL));
663		}
664		mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge,
665		    sizeof(conn->conn_mutual_challenge));
666		snprintf(mutual_chap_i, sizeof(mutual_chap_i),
667		    "%d", conn->conn_mutual_id);
668		keys_add(request_keys, "CHAP_I", mutual_chap_i);
669		keys_add(request_keys, "CHAP_C", mutual_chap_c);
670		free(mutual_chap_c);
671	}
672
673	keys_save(request_keys, request);
674	keys_delete(request_keys);
675	pdu_send(request);
676	pdu_delete(request);
677}
678
679static void
680login_verify_mutual(const struct pdu *response)
681{
682	struct connection *conn;
683	struct keys *response_keys;
684	const char *chap_n, *chap_r;
685	char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH];
686	size_t response_bin_len;
687	int error;
688
689	conn = response->pdu_connection;
690
691	response_keys = keys_new();
692	keys_load(response_keys, response);
693
694        chap_n = keys_find(response_keys, "CHAP_N");
695        if (chap_n == NULL)
696                log_errx(1, "received CHAP Response PDU without CHAP_N");
697        chap_r = keys_find(response_keys, "CHAP_R");
698        if (chap_r == NULL)
699                log_errx(1, "received CHAP Response PDU without CHAP_R");
700        error = login_hex2bin(chap_r, &response_bin, &response_bin_len);
701        if (error != 0)
702                log_errx(1, "received CHAP Response PDU with malformed CHAP_R");
703
704	if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) {
705		fail(conn, "Mutual CHAP failed");
706		log_errx(1, "mutual CHAP authentication failed: wrong user");
707	}
708
709	login_compute_md5(conn->conn_mutual_id,
710	    conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge,
711	    sizeof(conn->conn_mutual_challenge), expected_response_bin,
712	    sizeof(expected_response_bin));
713
714        if (memcmp(response_bin, expected_response_bin,
715            sizeof(expected_response_bin)) != 0) {
716		fail(conn, "Mutual CHAP failed");
717                log_errx(1, "mutual CHAP authentication failed: wrong secret");
718	}
719
720        keys_delete(response_keys);
721        free(response_bin);
722
723	log_debugx("mutual CHAP authentication succeeded");
724}
725
726static void
727login_chap(struct connection *conn)
728{
729	struct pdu *response;
730
731	log_debugx("beginning CHAP authentication; sending CHAP_A");
732	login_send_chap_a(conn);
733
734	log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I");
735	response = login_receive(conn, false);
736
737	log_debugx("sending CHAP_N/CHAP_R");
738	login_send_chap_r(response);
739	pdu_delete(response);
740
741	/*
742	 * XXX: Make sure this is not susceptible to MITM.
743	 */
744
745	log_debugx("waiting for CHAP result");
746	response = login_receive(conn, false);
747	if (conn->conn_conf.isc_mutual_user[0] != '\0')
748		login_verify_mutual(response);
749	pdu_delete(response);
750
751	log_debugx("CHAP authentication done");
752}
753
754void
755login(struct connection *conn)
756{
757	struct pdu *request, *response;
758	struct keys *request_keys, *response_keys;
759	struct iscsi_bhs_login_request *bhslr;
760	struct iscsi_bhs_login_response *bhslr2;
761	const char *auth_method;
762	int i;
763
764	log_debugx("beginning Login phase; sending Login PDU");
765	request = login_new_request(conn);
766
767	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
768
769	request_keys = keys_new();
770	if (conn->conn_conf.isc_mutual_user[0] != '\0') {
771		keys_add(request_keys, "AuthMethod", "CHAP");
772	} else if (conn->conn_conf.isc_user[0] != '\0') {
773		/*
774		 * Give target a chance to skip authentication if it
775		 * doesn't feel like it.
776		 *
777		 * None is first, CHAP second; this is to work around
778		 * what seems to be LIO (Linux target) bug: otherwise,
779		 * if target is configured with no authentication,
780		 * and we are configured to authenticate, the target
781		 * will erroneously respond with AuthMethod=CHAP
782		 * instead of AuthMethod=None, and will subsequently
783		 * fail the connection.  This usually happens with
784		 * Discovery sessions, which default to no authentication.
785		 */
786		keys_add(request_keys, "AuthMethod", "None,CHAP");
787	} else {
788		keys_add(request_keys, "AuthMethod", "None");
789	}
790	keys_add(request_keys, "InitiatorName",
791	    conn->conn_conf.isc_initiator);
792	if (conn->conn_conf.isc_initiator_alias[0] != '\0') {
793		keys_add(request_keys, "InitiatorAlias",
794		    conn->conn_conf.isc_initiator_alias);
795	}
796	if (conn->conn_conf.isc_discovery == 0) {
797		keys_add(request_keys, "SessionType", "Normal");
798		keys_add(request_keys,
799		    "TargetName", conn->conn_conf.isc_target);
800	} else {
801		keys_add(request_keys, "SessionType", "Discovery");
802	}
803	keys_save(request_keys, request);
804	keys_delete(request_keys);
805	pdu_send(request);
806	pdu_delete(request);
807
808	response = login_receive(conn, true);
809
810	response_keys = keys_new();
811	keys_load(response_keys, response);
812
813	for (i = 0; i < KEYS_MAX; i++) {
814		if (response_keys->keys_names[i] == NULL)
815			break;
816
817		/*
818		 * Not interested in AuthMethod at this point; we only need
819		 * to parse things such as TargetAlias.
820		 *
821		 * XXX: This is somewhat ugly.  We should have a way to apply
822		 * 	all the keys to the session and use that by default
823		 * 	instead of discarding them.
824		 */
825		if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0)
826			continue;
827
828		login_negotiate_key(conn,
829		    response_keys->keys_names[i], response_keys->keys_values[i]);
830	}
831
832	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
833	if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 &&
834	    login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
835		if (conn->conn_conf.isc_mutual_user[0] != '\0') {
836			log_errx(1, "target requested transition "
837			    "to operational negotiation, but we require "
838			    "mutual CHAP");
839		}
840
841		log_debugx("target requested transition "
842		    "to operational negotiation");
843		keys_delete(response_keys);
844		pdu_delete(response);
845		login_negotiate(conn);
846		return;
847	}
848
849	auth_method = keys_find(response_keys, "AuthMethod");
850	if (auth_method == NULL)
851		log_errx(1, "received response without AuthMethod");
852	if (strcmp(auth_method, "None") == 0) {
853		if (conn->conn_conf.isc_mutual_user[0] != '\0') {
854			log_errx(1, "target does not require authantication, "
855			    "but we require mutual CHAP");
856		}
857
858		log_debugx("target does not require authentication");
859		keys_delete(response_keys);
860		pdu_delete(response);
861		login_negotiate(conn);
862		return;
863	}
864
865	if (strcmp(auth_method, "CHAP") != 0) {
866		fail(conn, "Unsupported AuthMethod");
867		log_errx(1, "received response "
868		    "with unsupported AuthMethod \"%s\"", auth_method);
869	}
870
871	if (conn->conn_conf.isc_user[0] == '\0' ||
872	    conn->conn_conf.isc_secret[0] == '\0') {
873		fail(conn, "Authentication required");
874		log_errx(1, "target requests CHAP authentication, but we don't "
875		    "have user and secret");
876	}
877
878	keys_delete(response_keys);
879	response_keys = NULL;
880	pdu_delete(response);
881	response = NULL;
882
883	login_chap(conn);
884	login_negotiate(conn);
885}
886