OpenSolaris_b135/cmd/iscsi/iscsitgtd/iscsi_login.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/iscsi_protocol.h>
#include <arpa/inet.h>
#include <iscsitgt_impl.h>
#include "queue.h"
#include "iscsi_conn.h"
#include "iscsi_sess.h"
#include "iscsi_login.h"
#include "iscsi_provider_impl.h"
#include "utility.h"
#include "target.h"
#include "isns_client.h"

typedef enum auth_action {
	LOGIN_NO_AUTH,
	LOGIN_AUTH,
	LOGIN_DROP
} auth_action_t;

/*
 * Forward declarations
 */
static iscsi_login_rsp_hdr_t *make_login_response(iscsi_conn_t *c,
    iscsi_login_hdr_t *lhp);
static void send_login_reject(iscsi_conn_t *c, iscsi_login_hdr_t *lhp,
    int err_code);
static Boolean_t check_for_valid_I_T(iscsi_conn_t *c);
static auth_action_t login_set_auth(iscsi_sess_t *s);

/*
 * iscsi_null_callback - This callback may be used under certain
 * conditions when authenticating a target, but I'm not sure what
 * we need to do here.
 */
/* ARGSUSED */
static void
iscsi_null_callback(void *user_handle, void *message_handle, int auth_status)
{
}

/*
 * iscsi_find_key_value -
 */
static int
iscsi_find_key_value(char *param, char *ihp, char *pdu_end,
    char **value_start, char **value_end)
{
	char *str = param;
	char *text = ihp;
	char *value = NULL;

	if (value_start)
		*value_start = NULL;
	if (value_end)
		*value_end = NULL;

	/*
	 * make sure they contain the same bytes
	 */
	while (*str) {
		if (text >= pdu_end) {
			return (0);
		}
		if (*text == '\0') {
			return (0);
		}
		if (*str != *text) {
			return (0);
		}
		str++;
		text++;
	}

	if ((text >= pdu_end) ||
	    (*text == '\0') ||
	    (*text != ISCSI_TEXT_SEPARATOR)) {
		return (0);
	}

	/*
	 * find the value
	 */
	value = text + 1;

	/*
	 * find the end of the value
	 */
	while ((text < pdu_end) && (*text))
		text++;

	if (value_start)
		*value_start = value;
	if (value_end)
		*value_end = text;

	return (1);
}

Boolean_t
iscsi_handle_login_pkt(iscsi_conn_t *c)
{
	iscsi_login_hdr_t	lh;
	iscsi_login_rsp_hdr_t	*rsp		= NULL;
	Boolean_t		rval		= False;
	IscsiAuthClient		*auth_client	= NULL;
	char			*text		= NULL;
	char			*end		= NULL;
	char			*text_rsp	= NULL;
	char			debug[128];
	int			debug_status	= 0;
	int			errcode		= 0;
	int			text_length	= 0;
	int			keytype		= 0;
	int			transit		= 0;
	int			rc		= 0;
	auth_action_t		auth_action	= LOGIN_DROP;
	tgt_node_t		*tnode		= NULL;

	if (read(c->c_fd, &lh, sizeof (lh)) != sizeof (lh)) {
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log,
		    "Header to small");
		return (False);
	}

	if ((lh.opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN_CMD) {
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  Wrong OP code for state (Got 0x%x, Expected 0x%x)",
		    c->c_num, lh.opcode, ISCSI_OP_LOGIN_CMD);
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		send_login_reject(c, &lh,
		    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
		    ISCSI_LOGIN_STATUS_INVALID_REQUEST);
		conn_state(c, T7);
		return (True);
	}

	if (ISCSI_LOGIN_COMMAND_ENABLED()) {
		uiscsiproto_t info;
		char nil = '\0';

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = &nil;
		info.uip_initiator = &nil;
		info.uip_lun = 0;

		info.uip_itt = lh.itt;
		info.uip_ttt = ISCSI_RSVD_TASK_TAG;

		info.uip_cmdsn = ntohl(lh.cmdsn);
		info.uip_statsn = ntohl(lh.expstatsn);
		info.uip_datasn = 0;

		info.uip_datalen = ntoh24(lh.dlength);
		info.uip_flags = lh.flags;

		ISCSI_LOGIN_COMMAND(&info);
	}

	if ((rval = session_alloc(c, lh.isid)) == False) {
		conn_state(c, T7);
		return (True);
	}

	connection_parameters_default(c);

	c->c_cid	= ntohl(lh.cid);
	c->c_statsn	= ntohl(lh.expstatsn);

	(void) pthread_mutex_lock(&c->c_sess->s_mutex);
	c->c_sess->s_cmdsn	= ntohl(lh.cmdsn);
	c->c_sess->s_seencmdsn	= ntohl(lh.cmdsn);
	(void) pthread_mutex_unlock(&c->c_sess->s_mutex);

	/*
	 * Is this a new session or an attempt to add a connection to
	 * an existing session.
	 */
	if (ntohs(lh.tsid) != 0) {

		/* Multiple connections per session not handled right now */
		conn_state(c, T7);
		return (True);
	}

	if ((rsp = make_login_response(c, &lh)) == NULL) {
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  Failed make_login_response", c->c_num);
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		return (False);
	}
	/* default is ISCSI_FLAG_LOGIN_TRANSIT, not good for login */
	rsp->flags = 0;

	if ((rsp->active_version < lh.min_version) ||
	    (rsp->active_version > lh.max_version)) {
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  Version: Active %d, min %d, max %d", c->c_num,
		    rsp->active_version, lh.min_version, lh.max_version);
		send_login_reject(c, &lh,
		    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
		    ISCSI_LOGIN_STATUS_NO_VERSION);
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		conn_state(c, T7);
		free(rsp);
		return (True);
	}

	if (lh.flags & ISCSI_FLAG_LOGIN_CONTINUE) {
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  Continuation pkt", c->c_num);
		queue_str(c->c_mgmtq, Q_CONN_LOGIN, msg_log, debug);
	}

	auth_client =
	    (c->c_sess->sess_auth.auth_buffers &&
	    c->c_sess->sess_auth.num_auth_buffers) ?
	    (IscsiAuthClient *) c->c_sess->sess_auth.auth_buffers[0].address :
	    NULL;

	if (c->auth_text != NULL)
		free(c->auth_text);
	c->auth_text_length = 0;

	transit = lh.flags & ISCSI_FLAG_LOGIN_TRANSIT;

	switch (ISCSI_LOGIN_CURRENT_STAGE(lh.flags)) {
	case ISCSI_SECURITY_NEGOTIATION_STAGE:

		/*
		 * Grab the parameters and create the response
		 * text.
		 */
		rval = parse_text(c, ntoh24(lh.dlength),
		    &text_rsp, &text_length, &errcode);
		if (rval == False) {
			send_login_reject(c, &lh, errcode);
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  SecurityNegotiation: parse_text"
			    " failed", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			break;
		}

		if ((rval = check_for_valid_I_T(c)) == False) {
			send_login_reject(c, &lh,
			    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
			    ISCSI_LOGIN_STATUS_INIT_ERR);
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  SecurityNegotiation: invalid I "
			    "or T", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			break;
		}

		auth_action = login_set_auth(c->c_sess);

		if (auth_action == LOGIN_NO_AUTH) {
			rsp->flags |= ISCSI_FLAG_LOGIN_TRANSIT;
			rsp->flags |= ISCSI_OP_PARMS_NEGOTIATION_STAGE;
			rval = add_text(&text_rsp, &text_length, "AuthMethod",
			    "None");
			if (rval == False) {
				send_login_reject(c, &lh,
				    (ISCSI_STATUS_CLASS_TARGET_ERR << 8) |
				    ISCSI_LOGIN_STATUS_TARGET_ERROR);
				(void) snprintf(debug, sizeof (debug),
				    "CON%x Norm: Failed to add AuthMethod=None",
				    c->c_num);
				queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log,
				    debug);
				conn_state(c, T7);
			}
			break;
		}

		if (auth_action == LOGIN_DROP) {
			send_login_reject(c, &lh,
			    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
			    ISCSI_LOGIN_STATUS_TGT_FORBIDDEN);
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  SecurityNegotiation: access denied",
			    c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			rval = False;
			break;
		}

		if (iscsiAuthClientRecvBegin(auth_client) !=
		    iscsiAuthStatusNoError) {
			(void) snprintf(debug, sizeof (debug), "CON%x  "
			    "login failed - authentication receive failed",
			    c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			break;
		}

		if (iscsiAuthClientRecvTransitBit(auth_client,
		    transit) != iscsiAuthStatusNoError) {
			(void) snprintf(debug, sizeof (debug),
			    "iscsi connection(%u) login failed - "
			    "authentication transmit failed", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			break;
		}

		/*
		 * scan the text data
		 */
		text = c->auth_text;
		end = text + c->auth_text_length;
more_text:
		while (text && (text < end)) {
			char *value = NULL;
			char *value_end = NULL;
			keytype = iscsiAuthKeyTypeNone;

			/*
			 * skip any NULs separating each text key=value pair
			 */
			while ((text < end) && (*text == '\0')) {
				text++;
			}
			if (text >= end) {
				break;
			}

			while (iscsiAuthClientGetNextKeyType(&keytype) ==
			    iscsiAuthStatusNoError) {
				char *key =
				    (char *)iscsiAuthClientGetKeyName(keytype);
				if ((key) &&
				    (iscsi_find_key_value(key, text, end,
				    &value, &value_end))) {
					(void) snprintf(debug, sizeof (debug),
					    "%s=%s", key, value);
					queue_str(c->c_mgmtq, Q_CONN_ERRS,
					    msg_log, debug);
					if (iscsiAuthClientRecvKeyValue(
					    auth_client, keytype, value)
					    != iscsiAuthStatusNoError) {
						(void) snprintf(debug,
						    sizeof (debug),
						    "iscsi connection(%u) login"
						    "failed - can't accept "
						    "%s in security stage",
						    c->c_num, text);
						queue_str(c->c_mgmtq,
						    Q_CONN_ERRS,
						    msg_log, debug);
					}
					text = value_end;
					goto more_text;
				}
			}
		}

		switch (iscsiAuthClientRecvEnd(auth_client, iscsi_null_callback,
		    (void *)c->c_sess, NULL)) {
		case iscsiAuthStatusContinue:
			/*
			 * continue sending PDUs
			 */
			break;

		case iscsiAuthStatusPass:
			c->c_auth_pass = 1;
			break;

		case iscsiAuthStatusInProgress:
			/*
			 * this should only occur if we were authenticating the
			 * target, which we don't do yet, so treat this as an
			 * error.
			 */
		case iscsiAuthStatusNoError:
			/*
			 * treat this as an error, since we should get a
			 * different code
			 */
		case iscsiAuthStatusError:
		case iscsiAuthStatusFail:
		default:
			debug_status = 0;

			(void) iscsiAuthClientGetDebugStatus(auth_client,
			    &debug_status);
			(void) snprintf(debug, sizeof (debug),
			    "iscsi connection(%u) authentication failed (%s)",
			    c->c_num, iscsiAuthClientDebugStatusToText(
			    debug_status));
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);

			send_login_reject(c, &lh,
			    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
			    ISCSI_LOGIN_STATUS_AUTH_FAILED);
			conn_state(c, T7);
			rval = False;
			break;
		}

		if (rval == False)
			break;

		keytype = iscsiAuthKeyTypeNone;
		rc = iscsiAuthClientSendTransitBit(auth_client, &transit);

		/*
		 * see if we're ready for a stage change
		 */
		if (rc == iscsiAuthStatusNoError) {
			if (transit) {
				rsp->flags = lh.flags;
			}

		} else {
			send_login_reject(c, &lh,
			    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
			    ISCSI_LOGIN_STATUS_AUTH_FAILED);
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  SecurityNegotiation: wants", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			rval = False;
		}

		/*
		 * enumerate all the keys the auth code might want to send
		 */
		while (iscsiAuthClientGetNextKeyType(&keytype) ==
		    iscsiAuthStatusNoError) {
			int present = 0;
			char *key = (char *)iscsiAuthClientGetKeyName(keytype);
			int key_length = key ? strlen(key) : 0;
			int pdu_length = ntoh24(rsp->dlength);
			char *auth_value = NULL;
			unsigned int max_length = ISCSI_DEFAULT_MAX_XMIT_SEG_LEN
			    - (pdu_length + key_length + 1); /* FIXME: check */

			/*
			 * add the key/value pairs the auth code wants to
			 * send directly to the PDU, since they could in
			 * theory be large.
			 */
			if ((auth_value = (char *)malloc(max_length)) ==
			    NULL) {
				send_login_reject(c, &lh,
				    (ISCSI_STATUS_CLASS_TARGET_ERR << 8) |
				    ISCSI_LOGIN_STATUS_TARGET_ERROR);

				(void) snprintf(debug, sizeof (debug),
				    "CON%x Norm: Failed alloc auth_key %S=%s",
				    c->c_num, key, auth_value);
				queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log,
				    debug);
				conn_state(c, T7);
				rval = False;
				break;
			}
			rc = iscsiAuthClientSendKeyValue(auth_client, keytype,
			    &present, auth_value, max_length);
			if ((rc == iscsiAuthStatusNoError) && present) {
				(void) snprintf(debug, sizeof (debug),
				    "key:%s, auth_value:%s\n", key, auth_value);
				queue_str(c->c_mgmtq, Q_CONN_LOGIN, msg_log,
				    debug);

				rval = add_text(&text_rsp, &text_length,
				    key, auth_value);
				if (rval == False) {
					send_login_reject(c, &lh,
					    (ISCSI_STATUS_CLASS_TARGET_ERR <<
					    8) |
					    ISCSI_LOGIN_STATUS_TARGET_ERROR);
					(void) snprintf(debug, sizeof (debug),
					    "CON%x Norm: Failed to add %S=%s",
					    c->c_num, key, auth_value);
					queue_str(c->c_mgmtq, Q_CONN_ERRS,
					    msg_log, debug);
					conn_state(c, T7);
				}
			}
			if (auth_value != NULL)
				free(auth_value);
		}

		break;

	case ISCSI_OP_PARMS_NEGOTIATION_STAGE:

		/*
		 * Gather up the parameters sent across and build a response
		 * based on any selection required.
		 */
		if ((rval = parse_text(c, ntoh24(lh.dlength), &text_rsp,
		    &text_length, &errcode)) == False) {

			send_login_reject(c, &lh, errcode);
			(void) snprintf(debug, sizeof (debug),
			    "CON%x  Norm: parse_text failed", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			break;
		}

		/*
		 * If the connection hasn't passed authentication and
		 * it's a normal session see if this connection MUST
		 * have gone through authentication first. If the
		 * initiator has a CHAP secret stored that means we
		 * want to validate.
		 */
		if ((c->c_auth_pass == 0) &&
		    (c->c_sess->s_type == SessionNormal)) {
			if ((tnode = find_target_node(c->c_sess->s_t_name)) ==
			    NULL) {
				send_login_reject(c, &lh,
				    (ISCSI_STATUS_CLASS_TARGET_ERR << 8) |
				    ISCSI_LOGIN_STATUS_TARGET_ERROR);
				(void) snprintf(debug, sizeof (debug),
				    "CON%x  No target node in login",
				    c->c_num);
				queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log,
				    debug);
				conn_state(c, T7);
			}

			/*
			 * check_access will return True if the initiator
			 * is required to use CHAP authentication. So if
			 * true and we're here it means that the initiator
			 * is trying to skip the authentication step.
			 */
			if (check_access(tnode, c->c_sess->s_i_name, True) ==
			    False) {
				send_login_reject(c, &lh,
				    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
				    ISCSI_LOGIN_STATUS_AUTH_FAILED);
				(void) snprintf(debug, sizeof (debug),
				    "CON%x  Authentication required for %s",
				    c->c_num, c->c_sess->s_i_name);
				queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log,
				    debug);
				conn_state(c, T7);
			}
		}

		if ((rval = check_for_valid_I_T(c)) == False) {
			send_login_reject(c, &lh,
			    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
			    ISCSI_LOGIN_STATUS_INIT_ERR);

			(void) snprintf(debug, sizeof (debug),
			    "CON%x  Norm: bad I or T", c->c_num);
			queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
			conn_state(c, T7);
			break;
		}

		/*
		 * We accept transition and stage information as is
		 * and echo it back because at this point there's no need
		 * to send a parameter to the Initiator and expect a
		 * reply.
		 */
		rsp->flags = lh.flags;

		break;

	case ISCSI_FULL_FEATURE_PHASE:
		/* can't jump directly to full feature phase */
		(void) snprintf(debug, sizeof (debug),
		    "CON%x  Protocol error: wrong stage", c->c_num);
		queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
		send_login_reject(c, &lh,
		    (ISCSI_STATUS_CLASS_INITIATOR_ERR << 8) |
		    ISCSI_LOGIN_STATUS_INIT_ERR);
		conn_state(c, T7);
		rval = False;
		break;

	default:
		/* just drop the connection since we don't know what's up */
		rval = False;
		break;
	}

	hton24(rsp->dlength, text_length);
	if ((rval == True) && (session_validate(c->c_sess) == True)) {

		if (ISCSI_LOGIN_RESPONSE_ENABLED()) {
			uiscsiproto_t info;
			char nil = '\0';

			info.uip_target_addr = &c->c_target_sockaddr;
			info.uip_initiator_addr = &c->c_initiator_sockaddr;

			info.uip_target = &nil;
			info.uip_initiator = c->c_sess->s_i_name;
			info.uip_lun = 0;

			info.uip_itt = rsp->itt;
			info.uip_ttt = ISCSI_RSVD_TASK_TAG;

			info.uip_cmdsn = ntohl(rsp->expcmdsn);
			info.uip_statsn = ntohl(rsp->statsn);
			info.uip_datasn = 0;

			info.uip_datalen = text_length;
			info.uip_flags = rsp->flags;

			ISCSI_LOGIN_RESPONSE(&info);
		}

		send_iscsi_pkt(c, (iscsi_hdr_t *)rsp, text_rsp);

		if ((lh.flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
		    (ISCSI_LOGIN_NEXT_STAGE(lh.flags) ==
		    ISCSI_FULL_FEATURE_PHASE)) {

			conn_state(c, T5);

			/*
			 * At this point we've completed the negotiation
			 * of all login parameters. Now we need to perform
			 * some quick boundary checks and then send a couple
			 * pieces of information to STE for it's use.
			 */
			c->c_max_burst_len = MIN(c->c_max_burst_len,
			    c->c_max_recv_data);
		}
	}

	if (text_rsp != NULL)
		free(text_rsp);
	if (rsp != NULL)
		free(rsp);

	return (rval);
}

/*
 * check_for_valid_I_T -- check to see if we have valid names
 *
 * This routine checks to see if we have received a valid InitiatorName
 * and TargetName which is the bare minimum which an Initiator must send
 * across during the login phase.
 */
static Boolean_t
check_for_valid_I_T(iscsi_conn_t *c)
{
	iscsi_sess_t	*s = c->c_sess;
	if (s->s_type == SessionDiscovery)
		return (s->s_i_name == NULL || strlen(s->s_i_name) == 0) ?
		    False : True;
	else
		return (s->s_t_name == NULL || strlen(s->s_t_name) == 0) ||
		    (s->s_i_name == NULL || strlen(s->s_i_name) == 0) ?
		    False : True;
}

static iscsi_login_rsp_hdr_t *
make_login_response(iscsi_conn_t *c, iscsi_login_hdr_t *lhp)
{
	iscsi_login_rsp_hdr_t	*r;

	if (lhp->tsid != 0)
		/* don't except existing sessions for now */
		return (NULL);

	r = (iscsi_login_rsp_hdr_t *)calloc(sizeof (*r), sizeof (char));
	if (r == NULL)
		return (NULL);

	bcopy(lhp->isid, r->isid, 6); /* 6 is defined by protocol */
	r->opcode		= ISCSI_OP_LOGIN_RSP;
	r->flags		= ISCSI_FLAG_LOGIN_TRANSIT;
	r->max_version		= ISCSI_MAX_VERSION;
	r->active_version	= ISCSI_MIN_VERSION;
	r->itt			= lhp->itt;

	/*
	 * As per section 10.13.3 of iSCSI RFC (3720), For a new session,
	 * the target MUST generate a non-zero TSIH and ONLY return it
	 * in the Login Final-Response
	 */
	if ((lhp->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
	    (ISCSI_LOGIN_NEXT_STAGE(lhp->flags) ==
	    ISCSI_FULL_FEATURE_PHASE))
		/*
		 * If this is the final Login Response, send the target
		 * calculated TSIH
		 */
		r->tsid		= htons(c->c_sess->s_tsid);
	else
		/*
		 * If this is not the final Login Response, send the TSIH value
		 * provided by the initiator.
		 */
		r->tsid		= lhp->tsid;

	(void) pthread_mutex_lock(&c->c_mutex);
	r->statsn		= htonl(c->c_statsn++);
	(void) pthread_mutex_unlock(&c->c_mutex);
	if (c->c_sess != NULL) {
		(void) pthread_mutex_lock(&c->c_sess->s_mutex);
		/* ---- cmdsn is not advanced during login ---- */
		r->expcmdsn	= htonl(c->c_sess->s_seencmdsn);
		r->maxcmdsn	= htonl(CMD_MAXOUTSTANDING +
		    c->c_sess->s_seencmdsn);
		(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
	}

	return (r);
}

static void
send_login_reject(iscsi_conn_t *c, iscsi_login_hdr_t *lhp, int err_code)
{
	iscsi_login_rsp_hdr_t	*r;

	if ((r = make_login_response(c, lhp)) == NULL)
		return;

	r->status_class = (err_code >> 8) & 0xff;
	r->status_detail = err_code & 0xff;

	if (ISCSI_LOGIN_RESPONSE_ENABLED()) {
		uiscsiproto_t info;
		char nil = '\0';

		info.uip_target_addr = &c->c_target_sockaddr;
		info.uip_initiator_addr = &c->c_initiator_sockaddr;

		info.uip_target = &nil;
		info.uip_initiator = &nil;
		info.uip_lun = 0;

		info.uip_itt = r->itt;
		info.uip_ttt = ISCSI_RSVD_TASK_TAG;

		info.uip_cmdsn = ntohl(r->expcmdsn);
		info.uip_statsn = ntohl(r->statsn);
		info.uip_datasn = 0;

		info.uip_datalen = ntoh24(r->dlength);
		info.uip_flags = r->flags;

		ISCSI_LOGIN_RESPONSE(&info);
	}

	(void) write(c->c_fd, r, sizeof (*r));
	free(r);
}

static auth_action_t
login_set_auth(iscsi_sess_t *s)
{
	tgt_node_t *xnInitiator = NULL;
	tgt_node_t *xnTarget = NULL;
	tgt_node_t *xnAcl = NULL;
	char *szIniAlias = NULL;
	char *szIscsiName = NULL;
	char *szChapName = NULL;
	char *szChapSecret = NULL;
	char *possible = NULL;
	iscsi_auth_t *sess_auth = &(s->sess_auth);
	int comp = 0;
	int username_len = 0;

	bzero(sess_auth->username_in, sizeof (sess_auth->username_in));
	bzero(sess_auth->password_in, sizeof (sess_auth->password_in));
	sess_auth->password_length_in = 0;

	/* Load alias, iscsi-name, chap-name, chap-secret from config file */
	while ((xnInitiator = tgt_node_next_child(main_config, XML_ELEMENT_INIT,
	    xnInitiator)) != NULL) {

		(void) tgt_find_value_str(xnInitiator, XML_ELEMENT_INIT,
		    &szIniAlias);

		if (tgt_find_value_str(xnInitiator, XML_ELEMENT_INAME,
		    &szIscsiName) == True) {

			comp = strcmp(s->s_i_name, szIscsiName);
			free(szIscsiName);
			szIscsiName = NULL;

			if (comp == 0) {

				if (tgt_find_value_str(xnInitiator,
				    XML_ELEMENT_CHAPNAME,
				    &szChapName) == True) {
					/*CSTYLED*/
					(void) strcpy(
					    (char *)sess_auth->username_in,
					    szChapName);
					username_len = strlen(szChapName);
					free(szChapName);
				}

				if (tgt_find_value_str(xnInitiator,
				    XML_ELEMENT_CHAPSECRET,
				    &szChapSecret) == True) {
					/*CSTYLED*/
					(void) strcpy(
					    (char *)sess_auth->password_in,
					    szChapSecret);
					sess_auth->password_length_in =
					    strlen(szChapSecret);
					free(szChapSecret);
				}
				break;
			}
		}
	}

	if (s->s_type == SessionDiscovery) {
		return (LOGIN_NO_AUTH);
	}

	if (s->s_t_name == NULL) {
		/*
		 * Should not happen for non-discovery session
		 */
		return (LOGIN_DROP);
	}

	/*
	 * If iSNS enabled set LOGIN_AUTH
	 */
	if (isns_enabled() == True) {
		if (username_len == 0)
			return (LOGIN_NO_AUTH);
		return (LOGIN_AUTH);
	}

	/*
	 * If no acc_list for current target
	 *    If no CHAP secret for the initiator, transit.
	 *    If CHAP secret exists for the initiator, it must be authed.
	 * If acc_list exists for the target, and
	 * If the initiator not in the list, drop it.
	 * If the initiator in the list, and
	 * If no CHAP name for the initiator, transit.
	 * If a CHAP name exists for the initiator, it must be authed.
	 */

	while ((xnTarget = tgt_node_next_child(targets_config, XML_ELEMENT_TARG,
	    xnTarget)) != NULL) {

		if ((tgt_find_value_str(xnTarget, XML_ELEMENT_INAME,
		    &szIscsiName) == False) || (szIscsiName == NULL)) {
			return (LOGIN_DROP);
		}

		comp = strcmp(szIscsiName, s->s_t_name);
		free(szIscsiName);
		szIscsiName = NULL;

		if (comp == 0) {

			if ((xnAcl = tgt_node_next(xnTarget,
			    XML_ELEMENT_ACLLIST, 0)) == NULL) {
				/*
				 * No acl_list found, return auth or no auth
				 */
				if (username_len == 0)
					return (LOGIN_NO_AUTH);
				return (LOGIN_AUTH);
			}

			/*
			 * This target has an access_list. Now compare
			 * those entries against the initiator who started
			 * this session.
			 */
			xnInitiator = NULL;
			while ((xnInitiator = tgt_node_next(xnAcl,
			    XML_ELEMENT_INIT, xnInitiator)) != NULL) {

				if ((tgt_find_value_str(xnInitiator,
				    XML_ELEMENT_INIT, &possible) == False) ||
				    (possible == NULL))
					continue;

				if (strcmp(szIniAlias, possible) == 0) {
					/*
					 * Found the initiator in acl-list,
					 * authentication needed
					 */
					free(possible);
					if (username_len == 0)
						return (LOGIN_NO_AUTH);
					else
						return (LOGIN_AUTH);
				}

				free(possible);
				possible = NULL;
			}
			/*
			 * Acl-list exists, while the initiator is not found in
			 * the list, we should drop the connection
			 */
			return (LOGIN_DROP);
		}
	}

	/* False means Need authentication */
	return (LOGIN_AUTH);
}