root/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c
/*
 * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2016 Nexenta Systems, Inc.
 */

#include <sys/cpuvar.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/scsi/generic/persist.h>
#include <sys/scsi/scsi_names.h>

#include <sys/socket.h>
#include <sys/strsubr.h>
#include <sys/sysmacros.h>
#include <sys/note.h>
#include <sys/sdt.h>
#include <sys/errno.h>

#include <sys/stmf.h>
#include <sys/stmf_ioctl.h>
#include <sys/portif.h>
#include <sys/idm/idm.h>
#include <sys/idm/idm_text.h>
#include <sys/idm/idm_so.h>

#define ISCSIT_LOGIN_SM_STRINGS
#include "iscsit.h"
#include "iscsit_auth.h"

typedef struct {
        list_node_t             le_ctx_node;
        iscsit_login_event_t    le_ctx_event;
        idm_pdu_t               *le_pdu;
} login_event_ctx_t;

#ifndef TRUE
#define TRUE B_TRUE
#endif

#ifndef FALSE
#define FALSE B_FALSE
#endif

#define DEFAULT_RADIUS_PORT     1812

static void
login_sm_complete(void *ict_void);

static void
login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict,
    login_event_ctx_t *ctx);

static void
login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx);

static void
login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx,
    iscsit_login_state_t new_state);

static void
login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu);

static idm_status_t
login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu);

static boolean_t
login_sm_is_last_response(idm_pdu_t *pdu);

static void
login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu);

static void
login_sm_send_next_response(iscsit_conn_t *ict, idm_pdu_t *pdu);

static void
login_sm_process_request(iscsit_conn_t *ict);

static idm_status_t
login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu);

static idm_status_t
login_sm_process_nvlist(iscsit_conn_t *ict);

static idm_status_t
login_sm_check_security(iscsit_conn_t *ict);

static idm_pdu_t *
login_sm_build_login_response(iscsit_conn_t *ict);

static void
login_sm_ffp_actions(iscsit_conn_t *ict);

static idm_status_t
login_sm_validate_initial_parameters(iscsit_conn_t *ict);

static idm_status_t
login_sm_session_bind(iscsit_conn_t *ict);

static idm_status_t
login_sm_set_auth(iscsit_conn_t *ict);

static idm_status_t
login_sm_session_register(iscsit_conn_t *ict);

static kv_status_t
iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name);

static kv_status_t
iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx);

static kv_status_t
iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx);

static kv_status_t
iscsit_reply_security_key(iscsit_conn_t *ict);

static kv_status_t
iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx);

static kv_status_t
iscsit_reply_numerical(iscsit_conn_t *ict,
    const char *nvp_name, const uint64_t value);

static kv_status_t
iscsit_reply_string(iscsit_conn_t *ict,
    const char *nvp_name, const char *text);

static kv_status_t
iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices,
    const idm_kv_xlate_t *ikvx);

static kv_status_t
iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value,
    const idm_kv_xlate_t *ikvx, boolean_t iscsit_value);

static kv_status_t
iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value,
    const idm_kv_xlate_t *ikvx,
    uint64_t iscsi_min_value, uint64_t iscsi_max_value,
    uint64_t iscsit_max_value);

static void
iscsit_process_negotiated_values(iscsit_conn_t *ict);

static void
login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status);

static idm_status_t
iscsit_add_declarative_keys(iscsit_conn_t *ict);

static char *
iscsit_fold_name(char *name, size_t *buflen);

uint64_t max_dataseglen_target = ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH;

/*
 * global mutex defined in iscsit.c to enforce
 * login_sm_session_bind as a critical section
 */
extern kmutex_t login_sm_session_mutex;

idm_status_t
iscsit_login_sm_init(iscsit_conn_t *ict)
{
        iscsit_conn_login_t *lsm = &ict->ict_login_sm;

        bzero(lsm, sizeof (iscsit_conn_login_t));

        (void) nvlist_alloc(&lsm->icl_negotiated_values, NV_UNIQUE_NAME,
            KM_SLEEP);

        /*
         * Hold connection until the login state machine completes
         */
        iscsit_conn_hold(ict);

        /*
         * Pre-allocating a login response PDU means we will always be
         * able to respond to a login request -- even if we can't allocate
         * a data buffer to hold the text responses we can at least send
         * a login failure.
         */
        lsm->icl_login_resp_tmpl = kmem_zalloc(sizeof (iscsi_login_rsp_hdr_t),
            KM_SLEEP);

        idm_sm_audit_init(&lsm->icl_state_audit);
        mutex_init(&lsm->icl_mutex, NULL, MUTEX_DEFAULT, NULL);
        list_create(&lsm->icl_login_events, sizeof (login_event_ctx_t),
            offsetof(login_event_ctx_t, le_ctx_node));
        list_create(&lsm->icl_pdu_list, sizeof (idm_pdu_t),
            offsetof(idm_pdu_t, isp_client_lnd));

        lsm->icl_login_state = ILS_LOGIN_INIT;
        lsm->icl_login_last_state = ILS_LOGIN_INIT;

        /*
         * Initialize operational parameters to default values.  Anything
         * we don't specifically negotiate stays at the default.
         */
        ict->ict_op.op_discovery_session = B_FALSE;
        ict->ict_op.op_initial_r2t = ISCSI_DEFAULT_INITIALR2T;
        ict->ict_op.op_immed_data = ISCSI_DEFAULT_IMMEDIATE_DATA;
        ict->ict_op.op_data_pdu_in_order = ISCSI_DEFAULT_DATA_PDU_IN_ORDER;
        ict->ict_op.op_data_sequence_in_order =
            ISCSI_DEFAULT_DATA_SEQUENCE_IN_ORDER;
        ict->ict_op.op_max_connections = ISCSI_DEFAULT_MAX_CONNECTIONS;
        ict->ict_op.op_max_recv_data_segment_length =
            ISCSI_DEFAULT_MAX_RECV_SEG_LEN;
        ict->ict_op.op_max_burst_length = ISCSI_DEFAULT_MAX_BURST_LENGTH;
        ict->ict_op.op_first_burst_length = ISCSI_DEFAULT_FIRST_BURST_LENGTH;
        ict->ict_op.op_default_time_2_wait = ISCSI_DEFAULT_TIME_TO_WAIT;
        ict->ict_op.op_default_time_2_retain = ISCSI_DEFAULT_TIME_TO_RETAIN;
        ict->ict_op.op_max_outstanding_r2t = ISCSI_DEFAULT_MAX_OUT_R2T;
        ict->ict_op.op_error_recovery_level =
            ISCSI_DEFAULT_ERROR_RECOVERY_LEVEL;

        return (IDM_STATUS_SUCCESS);
}

static void
login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status)
{
        iscsit_conn_t *ict = pdu->isp_private;

        /*
         * Check that this is a login pdu
         */
        ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0);
        idm_pdu_free(pdu);

        if ((status != IDM_STATUS_SUCCESS) ||
            (ict->ict_login_sm.icl_login_resp_err_class != 0)) {
                /*
                 * Transport or login error occurred.
                 */
                iscsit_login_sm_event(ict, ILE_LOGIN_ERROR, NULL);
        }
        iscsit_conn_rele(ict);
}

void
iscsit_login_sm_fini(iscsit_conn_t *ict)
{
        iscsit_conn_login_t *lsm = &ict->ict_login_sm;

        mutex_enter(&lsm->icl_mutex);
        list_destroy(&lsm->icl_pdu_list);
        list_destroy(&lsm->icl_login_events);

        kmem_free(lsm->icl_login_resp_tmpl, sizeof (iscsi_login_rsp_hdr_t));

        /* clean up the login response idm text buffer */
        if (lsm->icl_login_resp_itb != NULL) {
                idm_itextbuf_free(lsm->icl_login_resp_itb);
                lsm->icl_login_resp_itb = NULL;
        }

        nvlist_free(lsm->icl_negotiated_values);
        mutex_destroy(&lsm->icl_mutex);
}

void
iscsit_login_sm_event(iscsit_conn_t *ict, iscsit_login_event_t event,
    idm_pdu_t *pdu)
{
        /*
         * This is a bit ugly but if we're already in ILS_LOGIN_ERROR
         * or ILS_LOGIN_DONE then just drop any additional events.  They
         * won't change the state and it's possible we've already called
         * iscsit_login_sm_fini in which case the mutex is destroyed.
         */
        if ((ict->ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) ||
            (ict->ict_login_sm.icl_login_state == ILS_LOGIN_DONE))
                return;

        mutex_enter(&ict->ict_login_sm.icl_mutex);
        iscsit_login_sm_event_locked(ict, event, pdu);
        mutex_exit(&ict->ict_login_sm.icl_mutex);
}
void
iscsit_login_sm_event_locked(iscsit_conn_t *ict, iscsit_login_event_t event,
    idm_pdu_t *pdu)
{
        iscsit_conn_login_t *lsm = &ict->ict_login_sm;
        login_event_ctx_t *ctx;

        ASSERT(mutex_owned(&lsm->icl_mutex));
        ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP);

        ctx->le_ctx_event = event;
        ctx->le_pdu = pdu;

        list_insert_tail(&lsm->icl_login_events, ctx);

        /*
         * Use the icl_busy flag to keep the state machine single threaded.
         * This also serves as recursion avoidance since this flag will
         * always be set if we call login_sm_event from within the
         * state machine code.
         */
        if (!lsm->icl_busy) {
                lsm->icl_busy = B_TRUE;
                while (!list_is_empty(&lsm->icl_login_events)) {
                        ctx = list_head(&lsm->icl_login_events);
                        list_remove(&lsm->icl_login_events, ctx);
                        idm_sm_audit_event(&lsm->icl_state_audit,
                            SAS_ISCSIT_LOGIN, (int)lsm->icl_login_state,
                            (int)ctx->le_ctx_event, (uintptr_t)pdu);

                        /*
                         * If the lsm is in a terminal state, just drain
                         * any remaining events.
                         */
                        if ((lsm->icl_login_state == ILS_LOGIN_ERROR) ||
                            (lsm->icl_login_state == ILS_LOGIN_DONE)) {
                                kmem_free(ctx, sizeof (*ctx));
                                continue;
                        }
                        mutex_exit(&lsm->icl_mutex);
                        login_sm_event_dispatch(lsm, ict, ctx);
                        mutex_enter(&lsm->icl_mutex);
                }
                lsm->icl_busy = B_FALSE;

                /*
                 * When the state machine reaches ILS_LOGIN_DONE or
                 * ILS_LOGIN_ERROR state the login process has completed
                 * and it's time to cleanup.  The state machine code will
                 * mark itself "complete" when this happens.
                 *
                 * To protect against spurious events (which shouldn't
                 * happen) set icl_busy again.
                 */
                if (lsm->icl_login_complete) {
                        lsm->icl_busy = B_TRUE;
                        if (taskq_dispatch(iscsit_global.global_dispatch_taskq,
                            login_sm_complete, ict, DDI_SLEEP) ==
                            TASKQID_INVALID) {
                                cmn_err(CE_WARN, "iscsit_login_sm_event_locked:"
                                    " Failed to dispatch task");
                        }
                }
        }
}

static void
login_sm_complete(void *ict_void)
{
        iscsit_conn_t *ict = ict_void;

        /*
         * State machine has run to completion, resources
         * will be cleaned up when connection is destroyed.
         */
        iscsit_conn_rele(ict);
}

static void
login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict,
    login_event_ctx_t *ctx)
{
        idm_pdu_t *pdu = ctx->le_pdu; /* Only valid for some events */

        DTRACE_PROBE2(login__event, iscsit_conn_t *, ict,
            login_event_ctx_t *, ctx);

        IDM_SM_LOG(CE_NOTE, "login_sm_event_dispatch: ict %p event %s(%d)",
            (void *)ict,
            iscsit_ile_name[ctx->le_ctx_event], ctx->le_ctx_event);

        /* State independent actions */
        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                /* Perform basic sanity checks on the header */
                if (login_sm_req_pdu_check(ict, pdu) != IDM_STATUS_SUCCESS) {
                        idm_pdu_t *rpdu;

                        SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                            ISCSI_LOGIN_STATUS_INVALID_REQUEST);
                        /*
                         * If we haven't processed any PDU's yet then use
                         * this one as a template for the response
                         */
                        if (ict->ict_login_sm.icl_login_resp_tmpl->opcode == 0)
                                login_sm_handle_initial_login(ict, pdu);
                        rpdu = login_sm_build_login_response(ict);
                        login_sm_send_next_response(ict, rpdu);
                        idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
                        kmem_free(ctx, sizeof (*ctx));
                        return;
                }
                break;
        default:
                break;
        }

        /* State dependent actions */
        switch (lsm->icl_login_state) {
        case ILS_LOGIN_INIT:
                login_sm_init(ict, ctx);
                break;
        case ILS_LOGIN_WAITING:
                login_sm_waiting(ict, ctx);
                break;
        case ILS_LOGIN_PROCESSING:
                login_sm_processing(ict, ctx);
                break;
        case ILS_LOGIN_RESPONDING:
                login_sm_responding(ict, ctx);
                break;
        case ILS_LOGIN_RESPONDED:
                login_sm_responded(ict, ctx);
                break;
        case ILS_LOGIN_FFP:
                login_sm_ffp(ict, ctx);
                break;
        case ILS_LOGIN_DONE:
                login_sm_done(ict, ctx);
                break;
        case ILS_LOGIN_ERROR:
                login_sm_error(ict, ctx);
                break;
        }

        kmem_free(ctx, sizeof (*ctx));
}

static void
login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        idm_pdu_t *pdu;

        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                pdu = ctx->le_pdu;

                /*
                 * This is the first login PDU we've received so use
                 * it to build the login response template and set our CSG.
                 */
                login_sm_handle_initial_login(ict, pdu);

                /*
                 * Accumulate all the login PDU's that make up this
                 * request on a queue.
                 */
                mutex_enter(&ict->ict_login_sm.icl_mutex);
                list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
                mutex_exit(&ict->ict_login_sm.icl_mutex);

                if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
                        login_sm_send_ack(ict, pdu);
                        login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING);
                } else {
                        login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
                }
                break;
        case ILE_LOGIN_CONN_ERROR:
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        idm_pdu_t *pdu;

        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                pdu = ctx->le_pdu;
                mutex_enter(&ict->ict_login_sm.icl_mutex);
                list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
                mutex_exit(&ict->ict_login_sm.icl_mutex);
                if (!(pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE)) {
                        login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
                } else {
                        login_sm_send_ack(ict, pdu);
                }
                break;
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        case ILE_LOGIN_RESP_COMPLETE:
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RESP_READY:
                login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDING);
                break;
        case ILE_LOGIN_RCV:
                idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
                /*FALLTHROUGH*/
        case ILE_LOGIN_CONN_ERROR:
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        idm_pdu_t *pdu, *rpdu;

        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                pdu = ctx->le_pdu;
                /*
                 * We should only be in "responding" state if we have not
                 * sent the last PDU of a multi-PDU login response sequence.
                 * In that case we expect this received PDU to be an
                 * acknowledgement from the initiator (login PDU with C
                 * bit cleared and no data).  If it's the acknowledgement
                 * we are expecting then we send the next PDU in the login
                 * response sequence.  Otherwise it's a protocol error and
                 * the login fails.
                 */
                if (login_sm_validate_ack(ict, pdu) == IDM_STATUS_SUCCESS) {
                        rpdu = login_sm_build_login_response(ict);
                        login_sm_send_next_response(ict, rpdu);
                } else {
                        login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                }
                idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
                break;
        case ILE_LOGIN_FFP:
                login_sm_new_state(ict, ctx, ILS_LOGIN_FFP);
                break;
        case ILE_LOGIN_RESP_COMPLETE:
                login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDED);
                break;
        case ILE_LOGIN_CONN_ERROR:
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        idm_pdu_t               *pdu;
        iscsi_login_hdr_t       *lh;

        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                pdu = ctx->le_pdu;
                lh = (iscsi_login_hdr_t *)pdu->isp_hdr;
                /*
                 * Set the CSG, NSG and Transit bits based on the this PDU.
                 * The CSG already validated in login_sm_req_pdu_check().
                 * We'll clear the transit bit if we encounter any login
                 * parameters in the request that required an additional
                 * login transfer (i.e. no acceptable
                 * choices in range or we needed to change a boolean
                 * value from "Yes" to "No").
                 */
                ict->ict_login_sm.icl_login_csg =
                    ISCSI_LOGIN_CURRENT_STAGE(lh->flags);
                ict->ict_login_sm.icl_login_nsg =
                    ISCSI_LOGIN_NEXT_STAGE(lh->flags);
                ict->ict_login_sm.icl_login_transit =
                    lh->flags & ISCSI_FLAG_LOGIN_TRANSIT;
                mutex_enter(&ict->ict_login_sm.icl_mutex);
                list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
                mutex_exit(&ict->ict_login_sm.icl_mutex);
                if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
                        login_sm_send_ack(ict, pdu);
                        login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING);
                } else {
                        login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
                }
                break;
        case ILE_LOGIN_CONN_ERROR:
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RESP_COMPLETE:
                login_sm_new_state(ict, ctx, ILS_LOGIN_DONE);
                break;
        case ILE_LOGIN_CONN_ERROR:
        case ILE_LOGIN_ERROR:
                login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
                break;
        default:
                ASSERT(0);
        }

}

/*ARGSUSED*/
static void
login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        /* Terminal state, we should get no events */
        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                /*
                 * We've already processed everything we're going to
                 * process.  Drop any additional login PDU's.
                 */
                idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
                break;
        case ILE_LOGIN_CONN_ERROR:
                /* Don't care */
                break;
        default:
                ASSERT(0);
        }
}

/*ARGSUSED*/
static void
login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx)
{
        switch (ctx->le_ctx_event) {
        case ILE_LOGIN_RCV:
                /*
                 * We've already processed everything we're going to
                 * process.  Drop any additional login PDU's.
                 */
                idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
                break;
        case ILE_LOGIN_CONN_ERROR:
                /* Don't care */
                break;
        default:
                ASSERT(0);
        }
}

static void
login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx,
    iscsit_login_state_t new_state)
{
        iscsit_conn_login_t *lsm = &ict->ict_login_sm;
        idm_pdu_t *rpdu;

        /*
         * Validate new state
         */
        ASSERT(new_state != ILS_UNDEFINED);
        ASSERT3U(new_state, <, ILS_MAX_STATE);

        new_state = (new_state < ILS_MAX_STATE) ?
            new_state : ILS_UNDEFINED;

        IDM_SM_LOG(CE_NOTE, "login_sm_new_state: conn %p "
            "%s (%d) --> %s (%d)\n", (void *)ict->ict_ic,
            iscsit_ils_name[lsm->icl_login_state], lsm->icl_login_state,
            iscsit_ils_name[new_state], new_state);

        DTRACE_PROBE3(login__state__change,
            iscsit_conn_t *, ict, login_event_ctx_t *, ctx,
            iscsit_login_state_t, new_state);

        mutex_enter(&lsm->icl_mutex);
        idm_sm_audit_state_change(&lsm->icl_state_audit, SAS_ISCSIT_LOGIN,
            (int)lsm->icl_login_state, (int)new_state);
        lsm->icl_login_last_state = lsm->icl_login_state;
        lsm->icl_login_state = new_state;
        mutex_exit(&lsm->icl_mutex);

        /*
         * Tale of caution here. The use of new_state instead of using
         * lsm->icl_login_state is deliberate (which had been used originally).
         * Since the icl_mutex is dropped under the right circumstances
         * the login state changes between setting the state and examining
         * the state to proceed. No big surprise since the lock was being
         * used in the first place to prevent just that type of change.
         *
         * There has been a case where network errors occurred while a client
         * was attempting to reinstate the connection causing multiple
         * login packets to arrive into the state machine. Those multiple
         * packets which were processed incorrectly caused the reference
         * count on the connection to be one higher than it should be and
         * from then on the connection can't close correctly causing a hang.
         *
         * Upon examination of the core it was found that the connection
         * audit data had calls looking like:
         *    login_sm_event_dispatch
         *    login_sm_processing
         *    login_sm_new_state
         * That call sequence means the new state was/is ILS_LOGIN_ERROR
         * yet the audit trail continues with a call to
         *    login_sm_send_next_response
         * which could only occur if icl_login_state had changed. Had the
         * design of COMSTAR taken this into account the code would
         * originally have held the icl_mutex across the processing of the
         * state processing. Lock order and calls which sleep prevent that
         * from being possible. The next best solution is to use the local
         * variable which holds the state.
         */
        switch (new_state) {
        case ILS_LOGIN_WAITING:
                /* Do nothing, waiting for more login PDU's */
                break;
        case ILS_LOGIN_PROCESSING:
                /* All login PDU's received, process login request */
                login_sm_process_request(ict);
                break;
        case ILS_LOGIN_RESPONDING:
                rpdu = login_sm_build_login_response(ict);
                login_sm_send_next_response(ict, rpdu);
                break;
        case ILS_LOGIN_RESPONDED:
                /* clean up the login response idm text buffer */
                if (lsm->icl_login_resp_itb != NULL) {
                        idm_itextbuf_free(lsm->icl_login_resp_itb);
                        lsm->icl_login_resp_itb = NULL;
                }
                break;
        case ILS_LOGIN_FFP:
                login_sm_ffp_actions(ict);
                break;
        case ILS_LOGIN_DONE:
        case ILS_LOGIN_ERROR:
                /*
                 * Flag the terminal state for the dispatcher
                 */
                lsm->icl_login_complete = B_TRUE;
                break;
        case ILS_LOGIN_INIT: /* Initial state, can't return */
        default:
                ASSERT(0);
                /*NOTREACHED*/
        }
}

/*ARGSUSED*/
static void
login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        idm_pdu_t               *lack;

        /*
         * allocate the response pdu
         */
        lack = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
        idm_pdu_init(lack, ict->ict_ic, ict, login_resp_complete_cb);
        lack->isp_flags |= IDM_PDU_LOGIN_TX;

        /*
         * copy the response template into the response pdu
         */
        bcopy(lsm->icl_login_resp_tmpl, lack->isp_hdr, sizeof (iscsi_hdr_t));

        iscsit_conn_hold(ict);
        idm_pdu_tx(lack);
}

/*ARGSUSED*/
static idm_status_t
login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu)
{
        iscsi_hdr_t *ihp = pdu->isp_hdr;
        if (ihp->flags & ISCSI_FLAG_TEXT_CONTINUE) {
                return (IDM_STATUS_FAIL);
        }
        if (ntoh24(ihp->dlength) != 0) {
                return (IDM_STATUS_FAIL);
        }
        return (IDM_STATUS_SUCCESS);
}

static boolean_t
login_sm_is_last_response(idm_pdu_t *pdu)
{

        if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
                return (B_FALSE);
        }
        return (B_TRUE);
}


static void
login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu)
{
        iscsi_login_hdr_t *lh_req = (iscsi_login_hdr_t *)pdu->isp_hdr;
        iscsi_login_rsp_hdr_t *lh_resp =
            ict->ict_login_sm.icl_login_resp_tmpl;

        /*
         * First login PDU, this connection should not have a sesssion
         * associated.
         */
        ASSERT(ict->ict_sess == NULL);

        /*
         * Save off TSIH and ISID for later use in finding a session
         */
        ict->ict_login_sm.icl_cmdsn = ntohl(lh_req->cmdsn);
        ict->ict_login_sm.icl_tsih = ntohs(lh_req->tsid);
        bcopy(lh_req->isid, ict->ict_login_sm.icl_isid, ISCSI_ISID_LEN);

        /*
         * We'll need the CID as well
         */
        ict->ict_cid = ntohs(lh_req->cid);

        /*
         * Set the CSG, NSG and Transit bits based on the first PDU
         * in the login sequence.  The CSG already validated in
         * login_sm_req_pdu_check(). We'll clear the transit bit if
         * we encounter any login parameters in the request that
         * required an additional login transfer (i.e. no acceptable
         * choices in range or we needed to change a boolean
         * value from "Yes" to "No").
         */
        ict->ict_login_sm.icl_login_csg =
            ISCSI_LOGIN_CURRENT_STAGE(lh_req->flags);
        ict->ict_login_sm.icl_login_nsg =
            ISCSI_LOGIN_NEXT_STAGE(lh_req->flags);
        ict->ict_login_sm.icl_login_transit =
            lh_req->flags & ISCSI_FLAG_LOGIN_TRANSIT;

        /*
         * Initialize header for login reject response.  This will also
         * be copied for use as a template for other login responses
         */
        lh_resp->opcode = ISCSI_OP_LOGIN_RSP;
        lh_resp->max_version = ISCSIT_MAX_VERSION;

        /*
         * We already validated that we can support one of the initiator's
         * versions in login_sm_req_pdu_check().
         */
#if (ISCSIT_MAX_VERSION > 0)
        if (ISCSIT_MAX_VERSION >= lh_req->min_version) {
                lh_resp->active_version =
                    MIN(lh_req->max_version, ISCSIT_MAX_VERSION);
        } else {
                ASSERT(ISCSIT_MAX_VERSION <= lh_req->max_version);
                lh_resp->active_version = ISCSIT_MAX_VERSION;
        }
#endif

        lh_resp->hlength = 0; /* No AHS */
        bcopy(lh_req->isid, lh_resp->isid, ISCSI_ISID_LEN);
        lh_resp->tsid = lh_req->tsid;
        lh_resp->itt = lh_req->itt;

        /*
         * StatSn, ExpCmdSn and MaxCmdSn will be set immediately before
         * transmission
         */
}

static void
login_sm_send_next_response(iscsit_conn_t *ict, idm_pdu_t *pdu)
{
        iscsi_login_rsp_hdr_t *lh_resp = (iscsi_login_rsp_hdr_t *)pdu->isp_hdr;

        /* Make sure this PDU is part of the login phase */
        ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0);

        /*
         * Fill in header values
         */
        hton24(lh_resp->dlength, pdu->isp_datalen);

        /*
         * If the login is successful, this login response will contain
         * the next StatSN and advance the StatSN for the connection.
         */
        if (lh_resp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
                ASSERT(ict->ict_sess != NULL);

                if ((lh_resp->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
                    (ISCSI_LOGIN_NEXT_STAGE(lh_resp->flags) ==
                    ISCSI_FULL_FEATURE_PHASE) &&
                    !(lh_resp->flags & ISCSI_FLAG_LOGIN_CONTINUE)) {
                        iscsit_login_sm_event(ict, ILE_LOGIN_FFP, NULL);
                }
                if (login_sm_is_last_response(pdu) == B_TRUE) {
                        /*
                         * The last of a potentially mult-PDU response finished.
                         */
                        iscsit_login_sm_event(ict, ILE_LOGIN_RESP_COMPLETE,
                            NULL);
                }

                iscsit_conn_hold(ict);
                pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
                iscsit_pdu_tx(pdu);
        } else {
                /*
                 * If status_class != ISCSI_STATUS_CLASS_SUCCESS then
                 * StatSN is not valid and we can call idm_pdu_tx instead
                 * of iscsit_pdu_tx.  This is very good thing since in
                 * some cases of login failure we may not have a session.
                 * Since iscsit_calc_rspsn grabs the session mutex while
                 * it is retrieving values for expcmdsn and maxcmdsn this
                 * would cause a panic.
                 *
                 * Since we still want a value for expcmdsn, fill in an
                 * appropriate value based on the login request before
                 * sending the response. Cmdsn/expcmdsn do not advance during
                 * login phase.
                 */
                lh_resp->expcmdsn = htonl(ict->ict_login_sm.icl_cmdsn);
                lh_resp->maxcmdsn = htonl(ict->ict_login_sm.icl_cmdsn + 1);

                iscsit_conn_hold(ict);
                idm_pdu_tx(pdu);
        }

}

static void
login_sm_process_request(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        uint8_t                 error_class = 0;
        uint8_t                 error_detail = 0;

        /*
         * First walk all the PDU's that make up this login request
         * and compile all the iSCSI key-value pairs into nvlist format.
         */

        ASSERT(lsm->icl_request_nvlist == NULL);
        /* create an nvlist for request key/value pairs */
        if (idm_pdu_list_to_nvlist(&lsm->icl_pdu_list,
            &lsm->icl_request_nvlist, &error_detail) != IDM_STATUS_SUCCESS) {
                error_class = ISCSI_STATUS_CLASS_TARGET_ERR;
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                goto request_fail;
        }

        /* Allocate a new nvlist for response key/value pairs */
        ASSERT(lsm->icl_response_nvlist == NULL);
        if (nvlist_alloc(&lsm->icl_response_nvlist, NV_UNIQUE_NAME,
            KM_NOSLEEP) != 0) {
                error_class = ISCSI_STATUS_CLASS_TARGET_ERR;
                error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                goto request_fail;
        }

        /*
         * This would be a very good time to make sure we have
         * negotiated the required values for the login phase.  For
         * example we definitely should have defined InitiatorName,
         * and Target name regardless of our current login phase.
         */
        if (!ict->ict_op.op_initial_params_set) {
                if (login_sm_validate_initial_parameters(ict) !=
                    IDM_STATUS_SUCCESS) {
                        goto request_fail;
                }

                /*
                 * Now setup our session association.  This includes
                 * create a new session or looking up an existing session,
                 * and if this is not a discovery session then we will
                 * also register this session with STMF.
                 */
                if (login_sm_session_bind(ict) != IDM_STATUS_SUCCESS) {
                        goto request_fail;
                }

                if (login_sm_set_auth(ict) != IDM_STATUS_SUCCESS) {
                        goto request_fail;
                }

                /*
                 * Prepend TargetAlias and PortalGroupTag
                 */
                if (ict->ict_op.op_discovery_session == B_FALSE) {
                        if ((lsm->icl_auth.ca_tgt_alias[0]) != '\0') {
                                (void) iscsit_reply_string(ict,
                                    "TargetAlias",
                                    &lsm->icl_auth.ca_tgt_alias[0]);
                        }
                        (void) iscsit_reply_numerical(ict,
                            "TargetPortalGroupTag",
                            (uint64_t)lsm->icl_tpgt_tag);
                }

                ict->ict_op.op_initial_params_set = B_TRUE;
        }

        if (login_sm_process_nvlist(ict) != IDM_STATUS_SUCCESS) {
                goto request_fail;
        }

        if (login_sm_check_security(ict) != IDM_STATUS_SUCCESS) {
                goto request_fail;
        }

        /* clean up request_nvlist */
        if (lsm->icl_request_nvlist != NULL) {
                nvlist_free(lsm->icl_request_nvlist);
                lsm->icl_request_nvlist = NULL;
        }

        /* convert any responses to textbuf form */
        ASSERT(lsm->icl_login_resp_itb == NULL);
        if (lsm->icl_response_nvlist) {
                lsm->icl_login_resp_itb = idm_nvlist_to_itextbuf(
                    lsm->icl_response_nvlist);
                if (lsm->icl_login_resp_itb == NULL) {
                        /* Still need to send the resp so continue */
                        SET_LOGIN_ERROR(ict,
                            ISCSI_STATUS_CLASS_TARGET_ERR,
                            ISCSI_LOGIN_STATUS_NO_RESOURCES);
                }
                /* clean up response_nvlist */
                nvlist_free(lsm->icl_response_nvlist);
                lsm->icl_response_nvlist = NULL;
        }

        /* tell the state machine to send the textbuf */
        iscsit_login_sm_event(ict, ILE_LOGIN_RESP_READY, NULL);
        return;

request_fail:

        /* clean up request_nvlist and response_nvlist */
        if (lsm->icl_request_nvlist != NULL) {
                nvlist_free(lsm->icl_request_nvlist);
                lsm->icl_request_nvlist = NULL;
        }
        if (lsm->icl_response_nvlist != NULL) {
                nvlist_free(lsm->icl_response_nvlist);
                lsm->icl_response_nvlist = NULL;
        }
        /* Make sure we already set the login error */
        if (ict->ict_login_sm.icl_login_resp_err_class ==
            ISCSI_STATUS_CLASS_SUCCESS) {
                SET_LOGIN_ERROR(ict,
                    ISCSI_STATUS_CLASS_TARGET_ERR,
                    ISCSI_LOGIN_STATUS_TARGET_ERROR);
        }
        iscsit_login_sm_event(ict, ILE_LOGIN_RESP_READY, NULL);
}


static void
login_sm_ffp_actions(iscsit_conn_t *ict)
{
        iscsit_process_negotiated_values(ict);
}

static idm_status_t
login_sm_validate_initial_parameters(iscsit_conn_t *ict)
{
        int             nvrc;
        char            *string_val;
        char            *u8_iscsi_name;
        size_t          u8_iscsi_name_len;
        uint8_t         error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
        uint8_t         error_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
        idm_status_t    status = IDM_STATUS_FAIL;
        iscsit_conn_login_t *lsm = &ict->ict_login_sm;

        /*
         * Make sure we received the required information from the initial
         * login. Add these declaratives to the negotiated list and
         * remove them from the request list as we go. If anything fails,
         * the caller will clean-up the nvlists.
         */

        /*
         * Initiator name
         */
        if ((nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
            "InitiatorName", &string_val)) != 0) {
                goto initial_params_done;
        }

        u8_iscsi_name = iscsit_fold_name(string_val, &u8_iscsi_name_len);
        if (u8_iscsi_name == NULL)
                goto initial_params_done;
        nvrc = nvlist_add_string(lsm->icl_negotiated_values, "InitiatorName",
            u8_iscsi_name);
        kmem_free(u8_iscsi_name, u8_iscsi_name_len);
        if (nvrc != 0)
                goto initial_params_done;

        if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
            "InitiatorName", &string_val)) != 0) {
                goto initial_params_done;
        }
        lsm->icl_initiator_name = string_val;
        idm_conn_set_initiator_name(ict->ict_ic, lsm->icl_initiator_name);
        if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
            "InitiatorName", DATA_TYPE_STRING)) != 0) {
                goto initial_params_done;
        }

        /*
         * Session type
         */
        ict->ict_op.op_discovery_session = B_FALSE;
        nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
            "SessionType", &string_val);
        if (nvrc != ENOENT && nvrc != 0) {
                goto initial_params_done;
        }
        if (nvrc == 0) {
                if (strcmp(string_val, "Discovery") == 0) {
                        ict->ict_op.op_discovery_session = B_TRUE;
                } else if (strcmp(string_val, "Normal") != 0) {
                        goto initial_params_done;
                }
                if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values,
                    "SessionType", string_val)) != 0) {
                        goto initial_params_done;
                }
                if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
                    "SessionType", DATA_TYPE_STRING)) != 0) {
                        goto initial_params_done;
                }
        }

        /*
         * Must have either TargetName or SessionType==Discovery
         */
        lsm->icl_target_name = NULL;
        nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
            "TargetName", &string_val);
        if (nvrc != ENOENT && nvrc != 0) {
                goto initial_params_done;
        }
        if (nvrc == 0) {
                u8_iscsi_name = iscsit_fold_name(string_val,
                    &u8_iscsi_name_len);
                if (u8_iscsi_name == NULL)
                        goto initial_params_done;
                nvrc = nvlist_add_string(lsm->icl_negotiated_values,
                    "TargetName", u8_iscsi_name);
                kmem_free(u8_iscsi_name, u8_iscsi_name_len);
                if (nvrc != 0)
                        goto initial_params_done;
                if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
                    "TargetName", &string_val)) != 0) {
                        goto initial_params_done;
                }
                lsm->icl_target_name = string_val;
                idm_conn_set_target_name(ict->ict_ic, lsm->icl_target_name);
                if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
                    "TargetName", DATA_TYPE_STRING)) != 0) {
                        goto initial_params_done;
                }
        } else if (ict->ict_op.op_discovery_session == B_FALSE) {
                /*
                 * Missing target name
                 */
                goto initial_params_done;
        }

        idm_conn_set_isid(ict->ict_ic, lsm->icl_isid);
        (void) snprintf(ict->ict_ic->ic_tsih, ISCSI_MAX_TSIH_LEN + 1, "0x%04x",
            lsm->icl_tsih);

        IDM_SM_LOG(CE_NOTE, "conn %p: initiator=%s", (void *)ict->ict_ic,
            (lsm->icl_initiator_name == NULL) ? "N/A" :
            lsm->icl_initiator_name);
        IDM_SM_LOG(CE_NOTE, "conn %p: target=%s", (void *)ict->ict_ic,
            (lsm->icl_target_name == NULL) ? "N/A" :
            lsm->icl_target_name);
        IDM_SM_LOG(CE_NOTE, "conn %p: sessiontype=%s", (void *)ict->ict_ic,
            ict->ict_op.op_discovery_session ? "Discovery" : "Normal");

        /* Sucess */
        status = IDM_STATUS_SUCCESS;
        error_class = ISCSI_STATUS_CLASS_SUCCESS;
        error_detail = ISCSI_LOGIN_STATUS_ACCEPT;

initial_params_done:
        SET_LOGIN_ERROR(ict, error_class, error_detail);
        return (status);
}


/*
 * login_sm_session_bind
 *
 * This function looks at the data from the initial login request
 * of a new connection and either looks up and existing session,
 * creates a new session, or returns an error.  RFC3720 section 5.3.1
 * defines these rules:
 *
 * +------------------------------------------------------------------+
 * |ISID      | TSIH        | CID    |     Target action              |
 * +------------------------------------------------------------------+
 * |new       | non-zero    | any    |     fail the login             |
 * |          |             |        |     ("session does not exist") |
 * +------------------------------------------------------------------+
 * |new       | zero        | any    |     instantiate a new session  |
 * +------------------------------------------------------------------+
 * |existing  | zero        | any    |     do session reinstatement   |
 * |          |             |        |     (see section 5.3.5)        |
 * +------------------------------------------------------------------+
 * |existing  | non-zero    | new    |     add a new connection to    |
 * |          | existing    |        |     the session                |
 * +------------------------------------------------------------------+
 * |existing  | non-zero    |existing|     do connection reinstatement|
 * |          | existing    |        |    (see section 5.3.4)         |
 * +------------------------------------------------------------------+
 * |existing  | non-zero    | any    |         fail the login         |
 * |          | new         |        |     ("session does not exist") |
 * +------------------------------------------------------------------+
 *
 */

/*
 * Map an <ipv6,port> address to an <ipv4,port> address if possible.
 * Returns:
 *    1 - success
 *    0 - address not mapable
 */

int
iscsit_is_v4_mapped(struct sockaddr_storage *sa, struct sockaddr_storage *v4sa)
{
        struct sockaddr_in *sin;
        struct in_addr *in;
        struct sockaddr_in6 *sin6;
        struct in6_addr *in6;
        int ret = 0;

        sin6 = (struct sockaddr_in6 *)sa;
        in6 = &sin6->sin6_addr;
        if ((sa->ss_family == AF_INET6) &&
            (IN6_IS_ADDR_V4MAPPED(in6) || IN6_IS_ADDR_V4COMPAT(in6))) {
                sin = (struct sockaddr_in *)v4sa;
                in = &sin->sin_addr;
                v4sa->ss_family = AF_INET;
                sin->sin_port = sin6->sin6_port;
                IN6_V4MAPPED_TO_INADDR(in6, in);
                ret = 1;
        }
        return (ret);
}

static idm_status_t
login_sm_session_bind(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        iscsit_tgt_t            *tgt = NULL;
        iscsit_tpgt_t           *tpgt = NULL;
        iscsit_portal_t         *portal = NULL;
        iscsit_sess_t           *existing_sess = NULL;
        iscsit_sess_t           *new_sess = NULL;
        iscsit_conn_t           *existing_ict = NULL;
        uint8_t                 error_class;
        uint8_t                 error_detail;

        /*
         * The multi-threaded execution of binding login sessions to target
         * introduced race conditions in the session creation/binding and
         * allowed duplicate sessions to tbe created. The addition of the
         * global mutex login_sm_session_mutex makes this function single
         * threaded to avoid such race conditions. Although this causes
         * a small portion of the login to be serialized, it is unlikely
         * that there would be numerous simultaneous logins to become a
         * performance issue.
         */
        mutex_enter(&login_sm_session_mutex);

        /*
         * Look up target and then check if there are sessions or connections
         * that match this request (see below).  Any holds taken on objects
         * must be released at the end of the function (let's keep things
         * simple).
         *
         * If target name is set then we should have a corresponding target
         * context configured.
         */
        if (lsm->icl_target_name != NULL) {
                /*
                 * iscsit_tgt_lookup implicitly takes a ref on the target
                 */
                ISCSIT_GLOBAL_LOCK(RW_READER);
                tgt = iscsit_tgt_lookup_locked(lsm->icl_target_name);
                if (tgt == NULL) {
                        ISCSIT_GLOBAL_UNLOCK();
                        SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                            ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
                        goto session_bind_error;
                } else {
                        mutex_enter(&tgt->target_mutex);
                        tpgt = avl_first(&tgt->target_tpgt_list);

                        if (IS_DEFAULT_TPGT(tpgt)) {
                                lsm->icl_tpgt_tag = ISCSIT_DEFAULT_TPGT;
                        } else {
                                /*
                                 * Find the portal group tag for the
                                 * login response.
                                 */
                                struct sockaddr_storage v4sa, *sa;

                                sa = &ict->ict_ic->ic_laddr;
                                portal = iscsit_tgt_lookup_portal(tgt,
                                    sa, &tpgt);
                                if (portal == NULL &&
                                    iscsit_is_v4_mapped(sa, &v4sa)) {
                                        /*
                                         * Try again if the local address
                                         * was v6 mappable to v4.
                                         */
                                        portal = iscsit_tgt_lookup_portal(tgt,
                                            &v4sa, &tpgt);

                                }
                                if (portal == NULL) {
                                        /*
                                         * Initiator came in on wrong address
                                         */
                                        SET_LOGIN_ERROR(ict,
                                            ISCSI_STATUS_CLASS_INITIATOR_ERR,
                                            ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
                                        mutex_exit(&tgt->target_mutex);
                                        ISCSIT_GLOBAL_UNLOCK();
                                        goto session_bind_error;
                                }

                                /*
                                 * Need to release holds on the portal and
                                 * tpgt after processing is complete.
                                 */
                                lsm->icl_tpgt_tag = tpgt->tpgt_tag;
                                iscsit_portal_rele(portal);
                                iscsit_tpgt_rele(tpgt);
                        }

                        mutex_enter(&iscsit_global.global_state_mutex);
                        if ((tgt->target_state != TS_STMF_ONLINE) ||
                            ((iscsit_global.global_svc_state != ISE_ENABLED) &&
                            ((iscsit_global.global_svc_state != ISE_BUSY)))) {
                                mutex_exit(&iscsit_global.global_state_mutex);
                                SET_LOGIN_ERROR(ict,
                                    ISCSI_STATUS_CLASS_TARGET_ERR,
                                    ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
                                mutex_exit(&tgt->target_mutex);
                                ISCSIT_GLOBAL_UNLOCK();
                                goto session_bind_error;
                        }
                        mutex_exit(&iscsit_global.global_state_mutex);
                        mutex_exit(&tgt->target_mutex);
                        ISCSIT_GLOBAL_UNLOCK();
                }
        }

        ASSERT((tgt != NULL) || (ict->ict_op.op_discovery_session == B_TRUE));

        /*
         * Check if there is an existing session matching this ISID.  If
         * tgt == NULL then we'll look for the session on the global list
         * of discovery session.  If we find a session then the ISID
         * exists.
         */
        existing_sess = iscsit_tgt_lookup_sess(tgt, lsm->icl_initiator_name,
            lsm->icl_isid, lsm->icl_tsih, lsm->icl_tpgt_tag);
        if (existing_sess != NULL) {
                existing_ict = iscsit_sess_lookup_conn(existing_sess,
                    ict->ict_cid);
        }

        /*
         * If this is a discovery session, make sure it has appropriate
         * parameters.
         */
        if ((ict->ict_op.op_discovery_session == B_TRUE) &&
            ((lsm->icl_tsih != ISCSI_UNSPEC_TSIH) || (existing_sess != NULL))) {
                /* XXX Do we need to check for existing ISID (sess != NULL)? */
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                    ISCSI_LOGIN_STATUS_INVALID_REQUEST);
                goto session_bind_error;
        }

        /*
         * Check the two error conditions from the table.
         *
         * ISID=new, TSIH=non-zero
         */
        if ((existing_sess == NULL) && (lsm->icl_tsih != ISCSI_UNSPEC_TSIH)) {
                /* fail the login */
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                    ISCSI_LOGIN_STATUS_NO_SESSION);
                goto session_bind_error;
        }

        /* ISID=existing, TSIH=non-zero new */
        if ((existing_sess != NULL) && (lsm->icl_tsih != 0) &&
            (existing_sess->ist_tsih != lsm->icl_tsih)) {
                /* fail the login */
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                    ISCSI_LOGIN_STATUS_NO_SESSION);
                goto session_bind_error;
        }

        /*
         * Handle the remaining table cases in order
         */
        if (existing_sess == NULL) {
                /* Should have caught this above */
                ASSERT(lsm->icl_tsih == ISCSI_UNSPEC_TSIH);
                /*
                 * ISID=new, TSIH=zero --> instantiate a new session
                 */
                new_sess = iscsit_sess_create(tgt, ict, lsm->icl_cmdsn,
                    lsm->icl_isid, lsm->icl_tpgt_tag, lsm->icl_initiator_name,
                    lsm->icl_target_name, &error_class, &error_detail);
                ASSERT(new_sess != NULL);

                /* Session create may have failed even if it returned a value */
                if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
                        SET_LOGIN_ERROR(ict, error_class, error_detail);
                        goto session_bind_error;
                }

                /*
                 * If we don't already have an STMF session and this is not
                 * a discovery session then we need to allocate and register
                 * one.
                 */
                if (!ict->ict_op.op_discovery_session) {
                        if (login_sm_session_register(ict) !=
                            IDM_STATUS_SUCCESS) {
                                /* login_sm_session_register sets error codes */
                                goto session_bind_error;
                        }
                }

        } else {
                if (lsm->icl_tsih == ISCSI_UNSPEC_TSIH) {
                        /*
                         * ISID=existing, TSIH=zero --> Session reinstatement
                         */
                        new_sess = iscsit_sess_reinstate(tgt, existing_sess,
                            ict, &error_class, &error_detail);
                        ASSERT(new_sess != NULL);

                        if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
                                SET_LOGIN_ERROR(ict, error_class, error_detail);
                                goto session_bind_error;
                        }

                        /*
                         * If we don't already have an STMF session and this is
                         * not a discovery session then we need to allocate and
                         * register one.
                         */
                        if (!ict->ict_op.op_discovery_session) {
                                if (login_sm_session_register(ict) !=
                                    IDM_STATUS_SUCCESS) {
                                        /*
                                         * login_sm_session_register sets
                                         * error codes
                                         */
                                        goto session_bind_error;
                                }
                        }
                } else {
                        /*
                         * The following code covers these two cases:
                         * ISID=existing, TSIH=non-zero existing, CID=new
                         * --> add new connection to MC/S session
                         * ISID=existing, TSIH=non-zero existing, CID=existing
                         * --> do connection reinstatement
                         *
                         * Session continuation uses this path as well
                         */
                        cmn_err(CE_NOTE, "login_sm_session_bind: add new "
                            "conn/sess continue");
                        if (existing_ict != NULL) {
                                /*
                                 * ISID=existing, TSIH=non-zero existing,
                                 * CID=existing --> do connection reinstatement
                                 */
                                if (iscsit_conn_reinstate(existing_ict, ict) !=
                                    IDM_STATUS_SUCCESS) {
                                        /*
                                         * Most likely this means the connection
                                         * the initiator is trying to reinstate
                                         * is not in an acceptable state.
                                         */
                                        SET_LOGIN_ERROR(ict,
                                            ISCSI_STATUS_CLASS_INITIATOR_ERR,
                                            ISCSI_LOGIN_STATUS_INIT_ERR);
                                        goto session_bind_error;
                                }
                        }

                        iscsit_sess_sm_event(existing_sess, SE_CONN_IN_LOGIN,
                            ict);
                }
        }

        if (tgt != NULL)
                iscsit_tgt_rele(tgt);
        if (existing_sess != NULL)
                iscsit_sess_rele(existing_sess);
        if (existing_ict != NULL)
                iscsit_conn_rele(existing_ict);

        mutex_exit(&login_sm_session_mutex);
        return (IDM_STATUS_SUCCESS);

session_bind_error:
        if (tgt != NULL)
                iscsit_tgt_rele(tgt);
        if (existing_sess != NULL)
                iscsit_sess_rele(existing_sess);
        if (existing_ict != NULL)
                iscsit_conn_rele(existing_ict);

        /*
         * If session bind fails we will fail the login but don't destroy
         * the session until later.
         */
        mutex_exit(&login_sm_session_mutex);
        return (IDM_STATUS_FAIL);
}


static idm_status_t
login_sm_set_auth(iscsit_conn_t *ict)
{
        idm_status_t            idmrc = IDM_STATUS_SUCCESS;
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        iscsit_ini_t            *ini;
        iscsit_tgt_t            *tgt;
        char                    *auth = "";
        char                    *radiusserver = "";
        char                    *radiussecret = "";
        char                    *chapuser = "";
        char                    *chapsecret = "";
        char                    *targetchapuser = "";
        char                    *targetchapsecret = "";
        char                    *targetalias = "";
        int                     i;

        ISCSIT_GLOBAL_LOCK(RW_READER);

        /*
         * Set authentication method to none for discovery session.
         */
        if (ict->ict_op.op_discovery_session == B_TRUE) {
                lsm->icl_auth.ca_method_valid_list[0] = AM_NONE;
                ISCSIT_GLOBAL_UNLOCK();
                return (idmrc);
        }

        /*
         * Get all the authentication parameters we need -- since we hold
         * the global config lock we guarantee that the parameters will
         * be consistent with each other.
         */
        (void) nvlist_lookup_string(iscsit_global.global_props,
            PROP_AUTH, &auth);
        (void) nvlist_lookup_string(iscsit_global.global_props,
            PROP_RADIUS_SERVER, &radiusserver);
        (void) nvlist_lookup_string(iscsit_global.global_props,
            PROP_RADIUS_SECRET, &radiussecret);

        ini = iscsit_ini_lookup_locked(lsm->icl_initiator_name);
        if (ini != NULL) {
                /* Get Initiator CHAP parameters */
                (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_USER,
                    &chapuser);
                (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_SECRET,
                    &chapsecret);
        }

        tgt = ict->ict_sess->ist_tgt;
        if (tgt != NULL) {
                /* See if we have a target-specific authentication setting */
                (void) nvlist_lookup_string(tgt->target_props, PROP_AUTH,
                    &auth);
                /* Get target CHAP parameters */
                (void) nvlist_lookup_string(tgt->target_props,
                    PROP_TARGET_CHAP_USER, &targetchapuser);
                (void) nvlist_lookup_string(tgt->target_props,
                    PROP_TARGET_CHAP_SECRET, &targetchapsecret);
                /* Get alias */
                (void) nvlist_lookup_string(tgt->target_props,
                    PROP_ALIAS, &targetalias);
        }

        /* Set authentication method */
        i = 0;
        if (strcmp(auth, PA_AUTH_RADIUS) == 0) {
                /* CHAP authentication using RADIUS server */
                lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP;
                lsm->icl_auth.ca_use_radius = B_TRUE;
        } else if (strcmp(auth, PA_AUTH_CHAP) == 0) {
                /* Local CHAP authentication */
                lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP;
                lsm->icl_auth.ca_use_radius = B_FALSE;
        } else if ((strcmp(auth, PA_AUTH_NONE) == 0) ||
            (strcmp(auth, "") == 0)) {
                /* No authentication */
                lsm->icl_auth.ca_method_valid_list[i++] = AM_NONE;
        }

        /*
         * If initiator/target CHAP username is not set then use the
         * node name.  If lsm->icl_target_name == NULL then this is
         * a discovery session so we don't need to work about the target.
         */
        if (strcmp(chapuser, "") == 0) {
                (void) strlcpy(lsm->icl_auth.ca_ini_chapuser,
                    lsm->icl_initiator_name,
                    min(iscsitAuthStringMaxLength, MAX_ISCSI_NODENAMELEN));
        } else {
                (void) strlcpy(lsm->icl_auth.ca_ini_chapuser, chapuser,
                    iscsitAuthStringMaxLength);
        }
        if ((lsm->icl_target_name != NULL) &&
            (strcmp(targetchapuser, "") == 0)) {
                (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser,
                    lsm->icl_target_name,
                    min(iscsitAuthStringMaxLength, MAX_ISCSI_NODENAMELEN));
        } else {
                (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser,
                    targetchapuser, iscsitAuthStringMaxLength);
        }

        /*
         * Secrets are stored in base64-encoded format so we need to
         * decode them into binary form
         */
        if (strcmp(chapsecret, "") == 0) {
                lsm->icl_auth.ca_ini_chapsecretlen = 0;
        } else {
                if (iscsi_base64_str_to_binary(chapsecret,
                    strnlen(chapsecret, iscsitAuthStringMaxLength),
                    lsm->icl_auth.ca_ini_chapsecret, iscsitAuthStringMaxLength,
                    &lsm->icl_auth.ca_ini_chapsecretlen) != 0) {
                        cmn_err(CE_WARN, "Corrupted CHAP secret"
                            " for initiator %s", lsm->icl_initiator_name);
                        lsm->icl_auth.ca_ini_chapsecretlen = 0;
                }
        }
        if (strcmp(targetchapsecret, "") == 0) {
                lsm->icl_auth.ca_tgt_chapsecretlen = 0;
        } else {
                if (iscsi_base64_str_to_binary(targetchapsecret,
                    strnlen(targetchapsecret, iscsitAuthStringMaxLength),
                    lsm->icl_auth.ca_tgt_chapsecret, iscsitAuthStringMaxLength,
                    &lsm->icl_auth.ca_tgt_chapsecretlen) != 0) {
                        cmn_err(CE_WARN, "Corrupted CHAP secret"
                            " for target %s", lsm->icl_target_name);
                        lsm->icl_auth.ca_tgt_chapsecretlen = 0;
                }
        }
        if (strcmp(radiussecret, "") == 0) {
                lsm->icl_auth.ca_radius_secretlen = 0;
        } else {
                if (iscsi_base64_str_to_binary(radiussecret,
                    strnlen(radiussecret, iscsitAuthStringMaxLength),
                    lsm->icl_auth.ca_radius_secret, iscsitAuthStringMaxLength,
                    &lsm->icl_auth.ca_radius_secretlen) != 0) {
                        cmn_err(CE_WARN, "Corrupted RADIUS secret");
                        lsm->icl_auth.ca_radius_secretlen = 0;
                }
        }

        /*
         * Set alias
         */
        (void) strlcpy(lsm->icl_auth.ca_tgt_alias, targetalias,
            MAX_ISCSI_NODENAMELEN);

        /*
         * Now that authentication parameters are setup, validate the parameters
         * against the authentication mode
         * Decode RADIUS server value int lsm->icl_auth.ca_radius_server
         */
        if ((strcmp(auth, PA_AUTH_RADIUS) == 0) &&
            ((lsm->icl_auth.ca_radius_secretlen == 0) ||
            (strcmp(radiusserver, "") == 0) ||
            it_common_convert_sa(radiusserver,
            &lsm->icl_auth.ca_radius_server,
            DEFAULT_RADIUS_PORT) == NULL)) {
                cmn_err(CE_WARN, "RADIUS authentication selected "
                    "for target %s but RADIUS parameters are not "
                    "configured.", lsm->icl_target_name);
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
                    ISCSI_LOGIN_STATUS_TARGET_ERROR);
                idmrc = IDM_STATUS_FAIL;
        } else if ((strcmp(auth, PA_AUTH_CHAP) == 0) &&
            (lsm->icl_auth.ca_ini_chapsecretlen == 0)) {
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                    ISCSI_LOGIN_STATUS_AUTH_FAILED);
                idmrc = IDM_STATUS_FAIL;
        }

        ISCSIT_GLOBAL_UNLOCK();

        return (idmrc);
}


static idm_status_t
login_sm_session_register(iscsit_conn_t *ict)
{
        iscsit_sess_t           *ist = ict->ict_sess;
        stmf_scsi_session_t     *ss;
        iscsi_transport_id_t    *iscsi_tptid;
        uint16_t                ident_len, adn_len, tptid_sz;
        char                    prop_buf[KSTAT_STRLEN + 1];
        char                    peer_buf[IDM_SA_NTOP_BUFSIZ];

        /*
         * Hold target mutex until we have finished registering with STMF
         */
        mutex_enter(&ist->ist_tgt->target_mutex);
        if (ist->ist_tgt->target_state != TS_STMF_ONLINE) {
                mutex_exit(&ist->ist_tgt->target_mutex);
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
                    ISCSI_LOGIN_STATUS_TGT_REMOVED);
                return (IDM_STATUS_FAIL);
        }

        ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0,
            0);
        if (ss == NULL) {
                mutex_exit(&ist->ist_tgt->target_mutex);
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
                    ISCSI_LOGIN_STATUS_NO_RESOURCES);
                return (IDM_STATUS_FAIL);
        }

        ident_len = strlen(ist->ist_initiator_name) + 1;
        ss->ss_rport_id = kmem_zalloc(sizeof (scsi_devid_desc_t) +
            ident_len, KM_SLEEP);
        (void) strcpy((char *)ss->ss_rport_id->ident, ist->ist_initiator_name);
        ss->ss_rport_id->ident_length = ident_len - 1;
        ss->ss_rport_id->protocol_id = PROTOCOL_iSCSI;
        ss->ss_rport_id->piv = 1;
        ss->ss_rport_id->code_set = CODE_SET_ASCII;
        ss->ss_rport_id->association = ID_IS_TARGET_PORT;

        /* adn_len should be 4 byte aligned, SPC3 rev 23, section 7.54.6 */
        adn_len = (ident_len + 3) & ~ 3;
        tptid_sz = sizeof (iscsi_transport_id_t) - 1 + adn_len;
        ss->ss_rport = stmf_remote_port_alloc(tptid_sz);
        ss->ss_rport->rport_tptid->protocol_id = PROTOCOL_iSCSI;
        ss->ss_rport->rport_tptid->format_code = 0;
        iscsi_tptid = (iscsi_transport_id_t *)ss->ss_rport->rport_tptid;
        SCSI_WRITE16(&iscsi_tptid->add_len, adn_len);
        (void) strlcpy((char *)iscsi_tptid->iscsi_name,
            ist->ist_initiator_name, ident_len);

        ss->ss_lport = ist->ist_lport;

        if (stmf_register_scsi_session(ict->ict_sess->ist_lport, ss) !=
            STMF_SUCCESS) {
                mutex_exit(&ist->ist_tgt->target_mutex);
                kmem_free(ss->ss_rport_id,
                    sizeof (scsi_devid_desc_t) +
                    strlen(ist->ist_initiator_name) + 1);
                stmf_remote_port_free(ss->ss_rport);
                stmf_free(ss);
                SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
                    ISCSI_LOGIN_STATUS_TARGET_ERROR);
                return (IDM_STATUS_FAIL);
        }

        ss->ss_port_private = ict->ict_sess;
        ict->ict_sess->ist_stmf_sess = ss;
        mutex_exit(&ist->ist_tgt->target_mutex);
        (void) snprintf(prop_buf, sizeof (prop_buf), "peername_%"PRIxPTR"",
            (uintptr_t)ict->ict_sess);
        (void) idm_sa_ntop(&ict->ict_ic->ic_raddr, peer_buf,
            sizeof (peer_buf));
        (void) stmf_add_rport_info(ss, prop_buf, peer_buf);

        return (IDM_STATUS_SUCCESS);
}


static idm_status_t
login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu)
{
        uint8_t                 csg_req;
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        iscsi_login_hdr_t       *lh = (iscsi_login_hdr_t *)pdu->isp_hdr;
        iscsi_login_rsp_hdr_t *lh_resp = lsm->icl_login_resp_tmpl;

        /*
         * Check CSG
         */
        csg_req = ISCSI_LOGIN_CURRENT_STAGE(lh->flags);
        switch (csg_req) {
        case ISCSI_SECURITY_NEGOTIATION_STAGE:
        case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
                if ((csg_req != lsm->icl_login_csg) &&
                    (lsm->icl_login_state != ILS_LOGIN_INIT)) {
                        /*
                         * Inappropriate CSG change.  Initiator can only
                         * change CSG after we've responded with the
                         * transit bit set.  If we had responded with
                         * a CSG change previous we would have updated
                         * our copy of CSG.
                         *
                         * The exception is when we are in ILS_LOGIN_INIT
                         * state since we haven't determined our initial
                         * CSG value yet.
                         */
                        goto pdu_check_fail;
                }
                break;
        case ISCSI_FULL_FEATURE_PHASE:
        default:
                goto pdu_check_fail;
        }

        /*
         * If this is the first login PDU for a new connection then
         * the session will be NULL.
         */
        if (ict->ict_sess != NULL) {
                /*
                 * We've already created a session on a previous PDU.  Make
                 * sure this PDU is consistent with what we've already seen
                 */
                if ((ict->ict_cid != ntohs(lh->cid)) ||
                    (bcmp(ict->ict_sess->ist_isid, lh->isid,
                    ISCSI_ISID_LEN) != 0)) {
                        goto pdu_check_fail;
                }
        }

        /*
         * Make sure we are compatible with the version range
         */
#if (ISCSIT_MAX_VERSION > 0)
        if ((lh->min_version > ISCSIT_MAX_VERSION) ||
            (lh->max_version < ISCSIT_MIN_VERSION)) {
                goto pdu_check_fail;
        }
#endif

        /*
         * Just in case the initiator changes things up on us along the way
         * check against our active_version -- we can't change the active
         * version and the initiator is not *supposed* to change its
         * min_version and max_version values so this should never happen.
         * Of course we only do this if the response header template has
         * been built.
         */
        if ((lh_resp->opcode == ISCSI_OP_LOGIN_RSP) && /* header valid */
            ((lh->min_version > lh_resp->active_version) ||
            (lh->max_version < lh_resp->active_version))) {
                goto pdu_check_fail;
        }

        return (IDM_STATUS_SUCCESS);

pdu_check_fail:
        return (IDM_STATUS_FAIL);
}

static idm_status_t
login_sm_process_nvlist(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        char                    *nvp_name;
        nvpair_t                *nvp;
        nvpair_t                *next_nvp;
        nvpair_t                *negotiated_nvp;
        kv_status_t             kvrc;
        uint8_t                 error_class;
        uint8_t                 error_detail;
        idm_status_t            idm_status;

        error_class = ISCSI_STATUS_CLASS_SUCCESS;
        error_detail = ISCSI_LOGIN_STATUS_ACCEPT;

        /* First, request that the transport process the list */
        kvrc = idm_negotiate_key_values(ict->ict_ic, lsm->icl_request_nvlist,
            lsm->icl_response_nvlist, lsm->icl_negotiated_values);
        idm_kvstat_to_error(kvrc, &error_class, &error_detail);
        if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                idm_status = IDM_STATUS_FAIL;
                return (idm_status);
        }

        /* Ensure we clear transit bit if the transport layer has countered */
        if (kvrc == KV_HANDLED_NO_TRANSIT) {
                lsm->icl_login_transit = B_FALSE;
        }

        /* Prepend the declarative params */
        if (!ict->ict_op.op_declarative_params_set &&
            lsm->icl_login_csg == ISCSI_OP_PARMS_NEGOTIATION_STAGE) {
                if (iscsit_add_declarative_keys(ict) != IDM_STATUS_SUCCESS) {
                        idm_status = IDM_STATUS_FAIL;
                        return (idm_status);
                }
                ict->ict_op.op_declarative_params_set = B_TRUE;
        }

        /* Now, move on and process the rest of the pairs */
        nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, NULL);
        while (nvp != NULL) {
                next_nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, nvp);
                nvp_name = nvpair_name(nvp);
                /*
                 * If we've already agreed upon a value then make sure this
                 * is not attempting to change that value.  From RFC3270
                 * section 5.3:
                 *
                 * "Neither the initiator nor the target should attempt to
                 * declare or negotiate a parameter more than once during
                 * login except for responses to specific keys that
                 * explicitly allow repeated key declarations (e.g.,
                 * TargetAddress).  An attempt to renegotiate/redeclare
                 * parameters not specifically allowed MUST be detected
                 * by the initiator and target.  If such an attempt is
                 * detected by the target, the target MUST respond
                 * with Login reject (initiator error); ..."
                 */
                if (nvlist_lookup_nvpair(lsm->icl_negotiated_values,
                    nvp_name, &negotiated_nvp) == 0) {
                        kvrc = KV_HANDLED;
                } else {
                        kvrc = iscsit_handle_key(ict, nvp, nvp_name);
                }

                idm_kvstat_to_error(kvrc, &error_class, &error_detail);
                if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
                        break;
                }

                nvp = next_nvp;
        }

        if (error_class == ISCSI_STATUS_CLASS_SUCCESS) {
                idm_status = IDM_STATUS_SUCCESS;
        } else {
                /* supply login class/detail for login errors */
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                idm_status = IDM_STATUS_FAIL;
        }

        return (idm_status);
}

static idm_status_t
login_sm_check_security(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        conn_auth_t             *auth = &lsm->icl_auth;
        iscsit_auth_method_t    *am_list = &auth->ca_method_valid_list[0];
        kv_status_t             kvrc;
        uint8_t                 error_class;
        uint8_t                 error_detail;
        idm_status_t            idm_status;

        error_class = ISCSI_STATUS_CLASS_SUCCESS;
        error_detail = ISCSI_LOGIN_STATUS_ACCEPT;

        /* Check authentication status. */
        if (lsm->icl_login_csg == ISCSI_SECURITY_NEGOTIATION_STAGE) {
                /*
                 * We should have some authentication key/value pair(s)
                 * received from initiator and the authentication phase
                 * has been shifted when the key/value pair(s) are being
                 * handled in the previous call iscsit_handle_security_key.
                 * Now it turns to target to check the authentication phase
                 * and shift it after taking some authentication action.
                 */
                kvrc = iscsit_reply_security_key(ict);
                idm_kvstat_to_error(kvrc, &error_class, &error_detail);
        } else if (!ict->ict_login_sm.icl_auth_pass) {
                /*
                 * Check to see if the target allows initiators to bypass the
                 * security check.  If the target is configured to require
                 * authentication, we reject the connection.
                 */
                if (am_list[0] == AM_NONE || am_list[0] == 0) {
                        ict->ict_login_sm.icl_auth_pass = 1;
                } else {
                        error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
                        error_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED;
                }
        }

        if (error_class == ISCSI_STATUS_CLASS_SUCCESS) {
                idm_status = IDM_STATUS_SUCCESS;
        } else {
                /* supply login class/detail for login errors */
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                idm_status = IDM_STATUS_FAIL;
        }

        return (idm_status);
}

static idm_pdu_t *
login_sm_build_login_response(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        iscsi_login_rsp_hdr_t   *lh;
        int                     transit, text_transit = 1;
        idm_pdu_t               *login_resp;

        /*
         * Create a response PDU and fill it with as much of
         * the response text that will fit.
         */

        if (lsm->icl_login_resp_itb) {
                /* allocate a pdu with space for text */
                login_resp = idm_pdu_alloc(sizeof (iscsi_hdr_t),
                    ISCSI_DEFAULT_MAX_RECV_SEG_LEN);
                /* copy a chunk of text into the pdu */
                lsm->icl_login_resp_buf = idm_pdu_init_text_data(
                    login_resp, lsm->icl_login_resp_itb,
                    ISCSI_DEFAULT_MAX_RECV_SEG_LEN,
                    lsm->icl_login_resp_buf, &text_transit);
                if (text_transit) {
                        /* text buf has been consumed */
                        idm_itextbuf_free(lsm->icl_login_resp_itb);
                        lsm->icl_login_resp_itb = NULL;
                        lsm->icl_login_resp_buf = NULL;
                }
        } else {
                /* allocate a pdu for just a header */
                login_resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
        }
        /* finish initializing the pdu */
        idm_pdu_init(login_resp,
            ict->ict_ic, ict, login_resp_complete_cb);
        login_resp->isp_flags |= IDM_PDU_LOGIN_TX;

        /*
         * Use the BHS header values from the response template
         */
        bcopy(lsm->icl_login_resp_tmpl,
            login_resp->isp_hdr, sizeof (iscsi_login_rsp_hdr_t));

        lh = (iscsi_login_rsp_hdr_t *)login_resp->isp_hdr;

        /* Set error class/detail */
        lh->status_class = lsm->icl_login_resp_err_class;
        lh->status_detail = lsm->icl_login_resp_err_detail;
        /* Set CSG, NSG and Transit */
        lh->flags = 0;
        lh->flags |= lsm->icl_login_csg << 2;


        if (lh->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
                if (lsm->icl_login_transit &&
                    lsm->icl_auth_pass != 0) {
                        transit = 1;
                } else {
                        transit = 0;
                }
                /*
                 * inititalize the text data
                 */
                if (transit == 1 && text_transit == 1) {
                        lh->flags |= lsm->icl_login_nsg;
                        lsm->icl_login_csg = lsm->icl_login_nsg;
                        lh->flags |= ISCSI_FLAG_LOGIN_TRANSIT;
                } else {
                        lh->flags &= ~ISCSI_FLAG_LOGIN_TRANSIT;
                }

                /* If we are transitioning to FFP then set TSIH */
                if (transit && (lh->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
                    lsm->icl_login_csg == ISCSI_FULL_FEATURE_PHASE) {
                        lh->tsid = htons(ict->ict_sess->ist_tsih);
                }
        } else {
                login_resp->isp_data = 0;
                login_resp->isp_datalen = 0;
        }
        return (login_resp);
}

static kv_status_t
iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        const idm_kv_xlate_t    *ikvx;

        ikvx = idm_lookup_kv_xlate(nvp_name, strlen(nvp_name));
        if (ikvx->ik_key_id == KI_MAX_KEY) {
                /*
                 * Any key not understood by the acceptor may be igonred
                 * by the acceptor without affecting the basic function.
                 * However, the answer for a key not understood MUST be
                 * key=NotUnderstood.
                 */
                kvrc = iscsit_reply_string(ict, nvp_name,
                    ISCSI_TEXT_NOTUNDERSTOOD);
        } else {
                kvrc = iscsit_handle_common_key(ict, nvp, ikvx);
                if (kvrc == KV_UNHANDLED) {
                        switch (lsm->icl_login_csg) {
                        case ISCSI_SECURITY_NEGOTIATION_STAGE:
                                kvrc = iscsit_handle_security_key(
                                    ict, nvp, ikvx);
                                break;
                        case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
                                kvrc = iscsit_handle_operational_key(
                                    ict, nvp, ikvx);
                                break;
                        case ISCSI_FULL_FEATURE_PHASE:
                        default:
                                /* What are we doing here? */
                                ASSERT(0);
                                kvrc = KV_UNHANDLED;
                        }
                }
        }

        return (kvrc);
}

static kv_status_t
iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        char                    *string_val;
        int                     nvrc;

        switch (ikvx->ik_key_id) {
        case KI_INITIATOR_NAME:
        case KI_INITIATOR_ALIAS:
                nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
                kvrc = idm_nvstat_to_kvstat(nvrc);
                break;
        case KI_TARGET_NAME:
                /* We'll validate the target during login_sm_session_bind() */
                nvrc = nvpair_value_string(nvp, &string_val);
                ASSERT(nvrc == 0); /* We built this nvlist */

                nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
                kvrc = idm_nvstat_to_kvstat(nvrc);
                break;
        case KI_TARGET_ALIAS:
        case KI_TARGET_ADDRESS:
        case KI_TARGET_PORTAL_GROUP_TAG:
                kvrc = KV_TARGET_ONLY; /* Only the target can declare this */
                break;
        case KI_SESSION_TYPE:
                /*
                 * If we don't receive this key on the initial login
                 * we assume this is a normal session.
                 */
                nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
                kvrc = idm_nvstat_to_kvstat(nvrc);
                nvrc = nvpair_value_string(nvp, &string_val);
                ASSERT(nvrc == 0); /* We built this nvlist */
                ict->ict_op.op_discovery_session =
                    strcmp(string_val, "Discovery") == 0 ? B_TRUE : B_FALSE;
                break;
        default:
                /*
                 * This is not really an error but we should
                 * leave this nvpair on the list since we
                 * didn't do anything with it.  Either
                 * the security or operational phase
                 * handling functions should process it.
                 */
                kvrc = KV_UNHANDLED;
                break;
        }

        return (kvrc);
}

static kv_status_t
iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        iscsit_auth_client_t    *client = &lsm->icl_auth_client;
        iscsikey_id_t           kv_id;
        kv_status_t             kvrc;
        iscsit_auth_handler_t   handler;

        /*
         * After all of security keys are handled, this function will
         * be called again to verify current authentication status
         * and perform some actual authentication work. At this time,
         * the nvp and ikvx will be passed in as NULLs.
         */
        if (ikvx != NULL) {
                kv_id = ikvx->ik_key_id;
        } else {
                kv_id = 0;
        }

        handler = iscsit_auth_get_handler(client, kv_id);
        if (handler) {
                kvrc = handler(ict, nvp, ikvx);
        } else {
                kvrc = KV_UNHANDLED; /* invalid request */
        }

        return (kvrc);
}

static kv_status_t
iscsit_reply_security_key(iscsit_conn_t *ict)
{
        return (iscsit_handle_security_key(ict, NULL, NULL));
}

static kv_status_t
iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp,
    const idm_kv_xlate_t *ikvx)
{
        kv_status_t             kvrc = KV_UNHANDLED;
        boolean_t               bool_val;
        uint64_t                num_val;
        int                     nvrc;

        /*
         * Retrieve values.  All value lookups are expected to succeed
         * since we build the nvlist while decoding the text buffer.  This
         * step is intended to eliminate some duplication of code (for example
         * we only need to code the numerical value lookup once).  We will
         * handle the values (if necessary) below.
         */
        switch (ikvx->ik_key_id) {
                /* Lists */
        case KI_HEADER_DIGEST:
        case KI_DATA_DIGEST:
                break;
                /* Booleans */
        case KI_INITIAL_R2T:
        case KI_IMMEDIATE_DATA:
        case KI_DATA_PDU_IN_ORDER:
        case KI_DATA_SEQUENCE_IN_ORDER:
        case KI_IFMARKER:
        case KI_OFMARKER:
                nvrc = nvpair_value_boolean_value(nvp, &bool_val);
                ASSERT(nvrc == 0); /* We built this nvlist */
                break;
                /* Numericals */
        case KI_MAX_CONNECTIONS:
        case KI_MAX_RECV_DATA_SEGMENT_LENGTH:
        case KI_MAX_BURST_LENGTH:
        case KI_FIRST_BURST_LENGTH:
        case KI_DEFAULT_TIME_2_WAIT:
        case KI_DEFAULT_TIME_2_RETAIN:
        case KI_MAX_OUTSTANDING_R2T:
        case KI_ERROR_RECOVERY_LEVEL:
                nvrc = nvpair_value_uint64(nvp, &num_val);
                ASSERT(nvrc == 0);
                break;
                /* Ranges */
        case KI_OFMARKERINT:
        case KI_IFMARKERINT:
                break;
        default:
                break;
        }

        /*
         * Now handle the values according to the key name.  Sometimes we
         * don't care what the value is -- in that case we just add the nvpair
         * to the negotiated values list.
         */
        switch (ikvx->ik_key_id) {
        case KI_HEADER_DIGEST:
                kvrc = iscsit_handle_digest(ict, nvp, ikvx);
                break;
        case KI_DATA_DIGEST:
                kvrc = iscsit_handle_digest(ict, nvp, ikvx);
                break;
        case KI_INITIAL_R2T:
                /* We *require* INITIAL_R2T=yes */
                kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
                    B_TRUE);
                break;
        case KI_IMMEDIATE_DATA:
                kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
                    bool_val);
                break;
        case KI_DATA_PDU_IN_ORDER:
                kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
                    B_TRUE);
                break;
        case KI_DATA_SEQUENCE_IN_ORDER:
                /* We allow any value for DATA_SEQUENCE_IN_ORDER */
                kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
                    bool_val);
                break;
        case KI_OFMARKER:
        case KI_IFMARKER:
                /* We don't support markers */
                kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
                    B_FALSE);
                break;
        case KI_MAX_CONNECTIONS:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_CONNECTIONS,
                    ISCSI_MAX_CONNECTIONS,
                    ISCSIT_MAX_CONNECTIONS);
                break;
                /* this is a declartive param */
        case KI_MAX_RECV_DATA_SEGMENT_LENGTH:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH,
                    ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH,
                    ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH);
                break;
        case KI_MAX_BURST_LENGTH:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_MAX_BURST_LENGTH,
                    ISCSI_MAX_BURST_LENGTH,
                    ISCSIT_MAX_BURST_LENGTH);
                break;
        case KI_FIRST_BURST_LENGTH:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_FIRST_BURST_LENGTH,
                    ISCSI_MAX_FIRST_BURST_LENGTH,
                    ISCSIT_MAX_FIRST_BURST_LENGTH);
                break;
        case KI_DEFAULT_TIME_2_WAIT:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_TIME2WAIT,
                    ISCSI_MAX_TIME2WAIT,
                    ISCSIT_MAX_TIME2WAIT);
                break;
        case KI_DEFAULT_TIME_2_RETAIN:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_TIME2RETAIN,
                    ISCSI_MAX_TIME2RETAIN,
                    ISCSIT_MAX_TIME2RETAIN);
                break;
        case KI_MAX_OUTSTANDING_R2T:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_MAX_OUTSTANDING_R2T,
                    ISCSI_MAX_OUTSTANDING_R2T,
                    ISCSIT_MAX_OUTSTANDING_R2T);
                break;
        case KI_ERROR_RECOVERY_LEVEL:
                kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
                    ISCSI_MIN_ERROR_RECOVERY_LEVEL,
                    ISCSI_MAX_ERROR_RECOVERY_LEVEL,
                    ISCSIT_MAX_ERROR_RECOVERY_LEVEL);
                break;
        case KI_OFMARKERINT:
        case KI_IFMARKERINT:
                kvrc = iscsit_reply_string(ict, ikvx->ik_key_name,
                    ISCSI_TEXT_IRRELEVANT);
                break;
        default:
                kvrc = KV_UNHANDLED; /* invalid request */
                break;
        }

        return (kvrc);
}

static kv_status_t
iscsit_reply_numerical(iscsit_conn_t *ict,
    const char *nvp_name, const uint64_t value)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        int                     nvrc;

        nvrc = nvlist_add_uint64(lsm->icl_response_nvlist,
            nvp_name, value);
        kvrc = idm_nvstat_to_kvstat(nvrc);

        return (kvrc);
}

static kv_status_t
iscsit_reply_string(iscsit_conn_t *ict,
    const char *nvp_name, const char *text)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        int                     nvrc;

        nvrc = nvlist_add_string(lsm->icl_response_nvlist,
            nvp_name, text);
        kvrc = idm_nvstat_to_kvstat(nvrc);

        return (kvrc);
}

static kv_status_t
iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices,
    const idm_kv_xlate_t *ikvx)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc = KV_VALUE_ERROR;
        int                     nvrc;
        nvpair_t                *digest_choice;
        char                    *digest_choice_string;

        /*
         * Need to add persistent config here if we want users to allow
         * disabling of digests on the target side.  You could argue that
         * this makes things too complicated... just let the initiator state
         * what it wants and we'll take it.  For now that's exactly what
         * we'll do.
         *
         * Basic digest negotiation happens here at iSCSI level.   IDM
         * can override this during negotiate_key_values phase to
         * decline to set up any digest processing.
         */
        digest_choice = idm_get_next_listvalue(choices, NULL);

        /*
         * Loop through all choices.  As soon as we find a choice
         * that we support add the value to our negotiated values list
         * and respond with that value in the login response.
         */
        while (digest_choice != NULL) {
                nvrc = nvpair_value_string(digest_choice,
                    &digest_choice_string);
                ASSERT(nvrc == 0);

                if ((strcasecmp(digest_choice_string, "crc32c") == 0) ||
                    (strcasecmp(digest_choice_string, "none") == 0)) {
                        /* Add to negotiated values list */
                        nvrc = nvlist_add_string(lsm->icl_negotiated_values,
                            ikvx->ik_key_name, digest_choice_string);
                        kvrc = idm_nvstat_to_kvstat(nvrc);
                        if (nvrc == 0) {
                                /* Add to login response list */
                                nvrc = nvlist_add_string(
                                    lsm->icl_response_nvlist,
                                    ikvx->ik_key_name, digest_choice_string);
                                kvrc = idm_nvstat_to_kvstat(nvrc);
                        }
                        break;
                }
                digest_choice = idm_get_next_listvalue(choices,
                    digest_choice);
        }

        if (digest_choice == NULL)
                kvrc = KV_VALUE_ERROR;

        return (kvrc);
}

static kv_status_t
iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value,
    const idm_kv_xlate_t *ikvx, boolean_t iscsit_value)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        int                     nvrc;

        if (ikvx->ik_declarative) {
                nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
        } else {
                if (value != iscsit_value) {
                        /* Respond back to initiator with our value */
                        value = iscsit_value;
                        nvrc = nvlist_add_boolean_value(
                            lsm->icl_negotiated_values,
                            ikvx->ik_key_name, value);
                        lsm->icl_login_transit = B_FALSE;
                } else {
                        /* Add this to our negotiated values */
                        nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values,
                            nvp);
                }

                /* Response of Simple-value Negotiation */
                if (nvrc == 0) {
                        nvrc = nvlist_add_boolean_value(
                            lsm->icl_response_nvlist, ikvx->ik_key_name, value);
                }
        }

        kvrc = idm_nvstat_to_kvstat(nvrc);

        return (kvrc);
}

static kv_status_t
iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value,
    const idm_kv_xlate_t *ikvx,
    uint64_t iscsi_min_value, uint64_t iscsi_max_value,
    uint64_t iscsit_max_value)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        kv_status_t             kvrc;
        int                     nvrc;

        /* Validate against standard */
        if ((value < iscsi_min_value) || (value > iscsi_max_value)) {
                kvrc = KV_VALUE_ERROR;
        } else if (ikvx->ik_declarative) {
                nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
                kvrc = idm_nvstat_to_kvstat(nvrc);
        } else {
                if (value > iscsit_max_value) {
                        /* Respond back to initiator with our value */
                        value = iscsit_max_value;
                        nvrc = nvlist_add_uint64(lsm->icl_negotiated_values,
                            ikvx->ik_key_name, value);
                        lsm->icl_login_transit = B_FALSE;
                } else {
                        /* Add this to our negotiated values */
                        nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values,
                            nvp);
                }

                /* Response of Simple-value Negotiation */
                if (nvrc == 0) {
                        nvrc = nvlist_add_uint64(lsm->icl_response_nvlist,
                            ikvx->ik_key_name, value);
                }
                kvrc = idm_nvstat_to_kvstat(nvrc);
        }

        return (kvrc);
}


static void
iscsit_process_negotiated_values(iscsit_conn_t *ict)
{
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        char                    *string_val;
        boolean_t               boolean_val;
        uint64_t                uint64_val;
        int                     nvrc;

        /* Let the IDM level activate its parameters first */
        idm_notice_key_values(ict->ict_ic, lsm->icl_negotiated_values);

        /*
         * Initiator alias and target alias
         */
        if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
            "InitiatorAlias", &string_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_sess->ist_initiator_alias =
                    kmem_alloc(strlen(string_val) + 1, KM_SLEEP);
                (void) strcpy(ict->ict_sess->ist_initiator_alias, string_val);
                if (ict->ict_sess->ist_stmf_sess)
                        ict->ict_sess->ist_stmf_sess->ss_rport_alias =
                            strdup(string_val);
        }

        if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
            "TargetAlias", &string_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_sess->ist_target_alias =
                    kmem_alloc(strlen(string_val) + 1, KM_SLEEP);
                (void) strcpy(ict->ict_sess->ist_target_alias, string_val);
        }

        /*
         * Operational parameters.  We process SessionType when it is
         * initially received since it is required on the initial login.
         */
        if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
            "InitialR2T", &boolean_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_initial_r2t = boolean_val;
        }

        if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
            "ImmediateData", &boolean_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_immed_data = boolean_val;
        }

        if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
            "DataPDUInOrder", &boolean_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_data_pdu_in_order = boolean_val;
        }

        if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
            "DataSequenceInOrder", &boolean_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_data_sequence_in_order = boolean_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "MaxConnections", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_max_connections = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "MaxRecvDataSegmentLength", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_max_recv_data_segment_length = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "MaxBurstLength", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_max_burst_length = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "FirstBurstLength", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_first_burst_length = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "DefaultTime2Wait", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_default_time_2_wait = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "DefaultTime2Retain", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_default_time_2_retain = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "MaxOutstandingR2T", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_max_outstanding_r2t = uint64_val;
        }

        if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
            "ErrorRecoveryLevel", &uint64_val)) != ENOENT) {
                ASSERT(nvrc == 0);
                ict->ict_op.op_error_recovery_level = uint64_val;
        }
}

static idm_status_t
iscsit_add_declarative_keys(iscsit_conn_t *ict)
{
        nvlist_t                *cfg_nv = NULL;
        kv_status_t             kvrc;
        int                     nvrc;
        iscsit_conn_login_t     *lsm = &ict->ict_login_sm;
        uint8_t                 error_class;
        uint8_t                 error_detail;
        idm_status_t            idm_status;

        if ((nvrc = nvlist_alloc(&cfg_nv, NV_UNIQUE_NAME, KM_NOSLEEP)) != 0) {
                kvrc = idm_nvstat_to_kvstat(nvrc);
                goto alloc_fail;
        }
        if ((nvrc = nvlist_add_uint64(cfg_nv, "MaxRecvDataSegmentLength",
            max_dataseglen_target)) != 0) {
                kvrc = idm_nvstat_to_kvstat(nvrc);
                goto done;
        }

        kvrc = idm_declare_key_values(ict->ict_ic, cfg_nv,
            lsm->icl_response_nvlist);
done:
        nvlist_free(cfg_nv);
alloc_fail:
        idm_kvstat_to_error(kvrc, &error_class, &error_detail);
        if (error_class == ISCSI_STATUS_CLASS_SUCCESS) {
                idm_status = IDM_STATUS_SUCCESS;
        } else {
                SET_LOGIN_ERROR(ict, error_class, error_detail);
                idm_status = IDM_STATUS_FAIL;
        }
        return (idm_status);
}

static char *
iscsit_fold_name(char *name, size_t *buflen)
{
        char            *ret;
        const char      *sns;
        int             errnum;
        int             flag = U8_TEXTPREP_NFKC;
        size_t          inlen, outlen, coff;

        if (name == NULL)
                return (NULL);

        /* Check for one of the supported name types */
        if (strncasecmp(name, SNS_EUI ".", strlen(SNS_EUI) + 1) == 0) {
                sns = SNS_EUI;
                *buflen = SNS_EUI_LEN_MAX + 1;
                flag |= U8_TEXTPREP_TOUPPER;
        } else if (strncasecmp(name, SNS_IQN ".", strlen(SNS_IQN) + 1) == 0) {
                sns = SNS_IQN;
                *buflen = SNS_IQN_LEN_MAX + 1;
                flag |= U8_TEXTPREP_TOLOWER;
        } else if (strncasecmp(name, SNS_NAA ".", strlen(SNS_NAA) + 1) == 0) {
                sns = SNS_NAA;
                *buflen = SNS_NAA_LEN_MAX + 1;
                flag |= U8_TEXTPREP_TOUPPER;
        } else {
                return (NULL);
        }

        ret = kmem_zalloc(*buflen, KM_SLEEP);
        coff = strlen(sns);
        inlen = strlen(name) - coff;
        outlen = *buflen - coff - 1;

        /* Fold the case and normalize string */
        if (u8_textprep_str(name + coff, &inlen, ret + coff, &outlen, flag,
            U8_UNICODE_320, &errnum) == (size_t)-1) {
                kmem_free(ret, *buflen);
                return (NULL);
        }

        /* Copy the name type prefix */
        bcopy(sns, ret, coff);

        return (ret);
}