root/usr/src/uts/common/io/scsi/adapters/iscsi/iscsiAuthClient.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 2000 by Cisco Systems, Inc.  All rights reserved.
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * This file implements the iSCSI CHAP authentication method based.
 * The code in this file is meant to be platform independent, and
 * makes use of only limited library functions, presently only string.h.
 * Platform dependent routines are  defined in iscsiAuthClient.h, but
 * implemented in another file.
 *
 * This code in this files assumes a single thread of execution
 * for each IscsiAuthClient structure, and does no locking.
 */

#include "iscsi.h"
#include "iscsiAuthClient.h"


struct iscsiAuthKeyInfo_t {
        const char *name;
};
typedef struct iscsiAuthKeyInfo_t IscsiAuthKeyInfo;


IscsiAuthClientGlobalStats iscsiAuthClientGlobalStats;

/*
 * Note: The ordering of this table must match the order
 *       defined by IscsiAuthKeyType in iscsiAuthClient.h.
 */
static IscsiAuthKeyInfo iscsiAuthClientKeyInfo[iscsiAuthKeyTypeMaxCount] = {
        {"AuthMethod"},
        {"CHAP_A"},
        {"CHAP_N"},
        {"CHAP_R"},
        {"CHAP_I"},
        {"CHAP_C"}
};

static const char iscsiAuthClientHexString[] = "0123456789abcdefABCDEF";
static const char iscsiAuthClientBase64String[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char iscsiAuthClientAuthMethodChapOptionName[] = "CHAP";


static int
iscsiAuthClientCheckString(const char *s,
    unsigned int maxLength, unsigned int *pOutLength)
{
        unsigned int length;

        if (!s) {
                return (TRUE);
        }

        for (length = 0; length < maxLength; length++) {
                if (*s++ == '\0') {
                        if (pOutLength) {
                                *pOutLength = length;
                        }
                        return (FALSE);
                }
        }

        return (TRUE);
}


static int
iscsiAuthClientStringCopy(char *stringOut, const char *stringIn,
    unsigned int length)
{
        if (!stringOut || !stringIn || length == 0) {
                return (TRUE);
        }

        while ((*stringOut++ = *stringIn++) != '\0') {
                if (--length == 0) {
                        stringOut--;
                        *stringOut = '\0';
                        return (TRUE);
                }
        }

        return (FALSE);
}


static int
iscsiAuthClientStringAppend(char *stringOut, const char *stringIn,
    unsigned int length)
{
        if (!stringOut || !stringIn || length == 0) {
                return (TRUE);
        }

        while (*stringOut++ != '\0') {
                if (--length == 0) {
                        stringOut--;
                        *stringOut = '\0';
                        return (TRUE);
                }
        }

        stringOut--;

        while ((*stringOut++ = *stringIn++) != '\0') {
                if (--length == 0) {
                        stringOut--;
                        *stringOut = '\0';
                        return (TRUE);
                }
        }

        return (FALSE);
}


static int
iscsiAuthClientStringIndex(const char *s, int c)
{
        int n = 0;

        while (*s != '\0') {
                if (*s++ == c) {
                        return (n);
                }
                n++;
        }

        return (-1);
}


static int
iscsiAuthClientCheckNodeType(int nodeType)
{
        if (nodeType == iscsiAuthNodeTypeInitiator ||
            nodeType == iscsiAuthNodeTypeTarget) {
                return (FALSE);
        }

        return (TRUE);
}


static int
iscsiAuthClientCheckVersion(int value)
{
        if (value == iscsiAuthVersionDraft8 || value == iscsiAuthVersionRfc) {

                return (FALSE);
        }

        return (TRUE);
}


static int
iscsiAuthClientCheckNegRole(int value)
{
        if (value == iscsiAuthNegRoleOriginator ||
            value == iscsiAuthNegRoleResponder) {
                return (FALSE);
        }

        return (TRUE);
}


static int
iscsiAuthClientCheckAuthMethodOption(int value)
{
        if (value == iscsiAuthOptionNone || value == iscsiAuthMethodChap) {

                return (FALSE);
        }

        return (TRUE);
}


static const char *
iscsiAuthClientAuthMethodOptionToText(IscsiAuthClient * client, int value)
{
        const char *s;

        switch (value) {
        case iscsiAuthOptionReject:
                s = client->rejectOptionName;
                break;

        case iscsiAuthOptionNone:
                s = client->noneOptionName;
                break;

        case iscsiAuthMethodChap:
                s = iscsiAuthClientAuthMethodChapOptionName;
                break;

        default:
                s = 0;
        }

        return (s);
}


static int
iscsiAuthClientCheckChapAlgorithmOption(int chapAlgorithm)
{
        if (chapAlgorithm == iscsiAuthOptionNone ||
            chapAlgorithm == iscsiAuthChapAlgorithmMd5) {
                return (FALSE);
        }

        return (TRUE);
}


static int
iscsiAuthClientDataToHex(unsigned char *data, unsigned int dataLength,
    char *text, unsigned int textLength)
{
        unsigned long n;

        if (!text || textLength == 0) {
                return (TRUE);
        }

        if (!data || dataLength == 0) {
                *text = '\0';
                return (TRUE);
        }

        if (textLength < 3) {
                *text = '\0';
                return (TRUE);
        }

        *text++ = '0';
        *text++ = 'x';

        textLength -= 2;

        while (dataLength > 0) {

                if (textLength < 3) {
                        *text = '\0';
                        return (TRUE);
                }

                n = *data++;
                dataLength--;

                *text++ = iscsiAuthClientHexString[(n >> 4) & 0xf];
                *text++ = iscsiAuthClientHexString[n & 0xf];

                textLength -= 2;
        }

        *text = '\0';

        return (FALSE);
}


static int
iscsiAuthClientDataToBase64(unsigned char *data, unsigned int dataLength,
    char *text, unsigned int textLength)
{
        unsigned long n;

        if (!text || textLength == 0) {
                return (TRUE);
        }

        if (!data || dataLength == 0) {
                *text = '\0';
                return (TRUE);
        }

        if (textLength < 3) {
                *text = '\0';
                return (TRUE);
        }

        *text++ = '0';
        *text++ = 'b';

        textLength -= 2;

        while (dataLength >= 3) {

                if (textLength < 5) {
                        *text = '\0';
                        return (TRUE);
                }

                n = *data++;
                n = (n << 8) | *data++;
                n = (n << 8) | *data++;
                dataLength -= 3;

                *text++ = iscsiAuthClientBase64String[(n >> 18) & 0x3f];
                *text++ = iscsiAuthClientBase64String[(n >> 12) & 0x3f];
                *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f];
                *text++ = iscsiAuthClientBase64String[n & 0x3f];

                textLength -= 4;
        }

        if (dataLength == 1) {

                if (textLength < 5) {
                        *text = '\0';
                        return (TRUE);
                }

                n = *data++;
                n = n << 4;

                *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f];
                *text++ = iscsiAuthClientBase64String[n & 0x3f];
                *text++ = '=';
                *text++ = '=';

        } else if (dataLength == 2) {

                if (textLength < 5) {
                        return (TRUE);
                }

                n = *data++;
                n = (n << 8) | *data++;
                n = n << 2;

                *text++ = iscsiAuthClientBase64String[(n >> 12) & 0x3f];
                *text++ = iscsiAuthClientBase64String[(n >> 6) & 0x3f];
                *text++ = iscsiAuthClientBase64String[n & 0x3f];
                *text++ = '=';
        }

        *text = '\0';

        return (FALSE);
}


static int
iscsiAuthClientDataToText(int base64, unsigned char *data,
    unsigned int dataLength, char *text, unsigned int textLength)
{
        int status;

        if (base64) {
                status = iscsiAuthClientDataToBase64(
                    data, dataLength, text, textLength);
        } else {
                status = iscsiAuthClientDataToHex(
                    data, dataLength, text, textLength);
        }

        return (status);
}


static int
iscsiAuthClientHexToData(const char *text, unsigned int textLength,
    unsigned char *data, unsigned int *pDataLength)
{
        int i;
        unsigned int n1;
        unsigned int n2;
        unsigned int dataLength = *pDataLength;

        if ((textLength % 2) == 1) {
                i = iscsiAuthClientStringIndex(iscsiAuthClientHexString,
                    *text++);
                if (i < 0) {
                        return (TRUE);  /* error, bad character */
                }

                if (i > 15)
                        i -= 6;
                n2 = i;

                if (dataLength < 1) {
                        return (TRUE);  /* error, too much data */
                }

                *data++ = n2;
                dataLength--;
        }

        while (*text != '\0') {

                i = iscsiAuthClientStringIndex(
                    iscsiAuthClientHexString, *text++);
                if (i < 0) {
                        return (TRUE);  /* error, bad character */
                }

                if (i > 15)
                        i -= 6;
                n1 = i;

                if (*text == '\0') {
                        return (TRUE);  /* error, odd string length */
                }

                i = iscsiAuthClientStringIndex(
                    iscsiAuthClientHexString, *text++);
                if (i < 0) {
                        return (TRUE);  /* error, bad character */
                }

                if (i > 15)
                        i -= 6;
                n2 = i;

                if (dataLength < 1) {
                        return (TRUE);  /* error, too much data */
                }

                *data++ = (n1 << 4) | n2;
                dataLength--;
        }

        if (dataLength >= *pDataLength) {
                return (TRUE);  /* error, no data */
        }

        *pDataLength = *pDataLength - dataLength;

        return (FALSE);         /* no error */
}


static int
iscsiAuthClientBase64ToData(const char *text, unsigned int textLength,
    unsigned char *data, unsigned int *pDataLength)
{
        int i;
        unsigned int n;
        unsigned int count;
        unsigned int dataLength = *pDataLength;

        textLength = textLength;        /* not used */

        n = 0;
        count = 0;

        while (*text != '\0' && *text != '=') {

                i = iscsiAuthClientStringIndex(
                    iscsiAuthClientBase64String, *text++);
                if (i < 0) {
                        return (TRUE);  /* error, bad character */
                }

                n = (n << 6 | (unsigned int)i);
                count++;

                if (count >= 4) {
                        if (dataLength < 3) {
                                return (TRUE);  /* error, too much data */
                        }
                        *data++ = n >> 16;
                        *data++ = n >> 8;
                        *data++ = n;
                        dataLength -= 3;
                        n = 0;
                        count = 0;
                }
        }

        while (*text != '\0') {
                if (*text++ != '=') {
                        return (TRUE);  /* error, bad pad */
                }
        }

        if (count == 0) {
                /*
                 * do nothing
                 */
                /* EMPTY */
        } else if (count == 2) {
                if (dataLength < 1) {
                        return (TRUE);  /* error, too much data */
                }
                n = n >> 4;
                *data++ = n;
                dataLength--;
        } else if (count == 3) {
                if (dataLength < 2) {
                        return (TRUE);  /* error, too much data */
                }
                n = n >> 2;
                *data++ = n >> 8;
                *data++ = n;
                dataLength -= 2;
        } else {
                return (TRUE);  /* bad encoding */
        }

        if (dataLength >= *pDataLength) {
                return (TRUE);  /* error, no data */
        }

        *pDataLength = *pDataLength - dataLength;

        return (FALSE);         /* no error */
}


static int
iscsiAuthClientTextToData(const char *text, unsigned char *data,
    unsigned int *dataLength)
{
        int status;
        unsigned int textLength;

        status = iscsiAuthClientCheckString(text,
            2 + 2 * iscsiAuthLargeBinaryMaxLength + 1, &textLength);

        if (status) {
                return (status);
        }

        if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
                /*
                 * skip prefix
                 */
                text += 2;
                textLength -= 2;
                status = iscsiAuthClientHexToData(text,
                    textLength, data, dataLength);
        } else if (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')) {
                /*
                 * skip prefix
                 */
                text += 2;
                textLength -= 2;
                status = iscsiAuthClientBase64ToData(text,
                    textLength, data, dataLength);
        } else {
                status = TRUE;  /* prefix not recognized. */
        }

        return (status);
}


static IscsiAuthDebugStatus
iscsiAuthClientChapComputeResponse(IscsiAuthClient * client,
    int remoteAuthentication, unsigned int id,
    unsigned char *challengeData, unsigned int challengeLength,
    unsigned char *responseData)
{
        unsigned char idData[1];
        IscsiAuthMd5Context context;
        unsigned char outData[iscsiAuthStringMaxLength];
        unsigned int outLength = iscsiAuthStringMaxLength;

        if (!client->passwordPresent) {
                return (iscsiAuthDebugStatusLocalPasswordNotSet);
        }

        iscsiAuthMd5Init(&context);

        /*
         * id byte
         */
        idData[0] = id;
        iscsiAuthMd5Update(&context, idData, 1);

        /*
         * decrypt password
         */
        if (iscsiAuthClientData(outData, &outLength,
            client->passwordData, client->passwordLength)) {

                return (iscsiAuthDebugStatusPasswordDecryptFailed);
        }

        if (!remoteAuthentication && !client->ipSec && outLength < 12) {
                return (iscsiAuthDebugStatusPasswordTooShortWithNoIpSec);
        }

        /*
         * shared secret
         */
        iscsiAuthMd5Update(&context, outData, outLength);

        /*
         * clear decrypted password
         */
        bzero(outData, iscsiAuthStringMaxLength);

        /*
         * challenge value
         */
        iscsiAuthMd5Update(&context, challengeData, challengeLength);

        iscsiAuthMd5Final(responseData, &context);

        return (iscsiAuthDebugStatusNotSet);    /* no error */
}


static void
iscsiAuthClientInitKeyBlock(IscsiAuthKeyBlock * keyBlock)
{
        char *stringBlock = keyBlock->stringBlock;

        bzero(keyBlock, sizeof (*keyBlock));
        keyBlock->stringBlock = stringBlock;
}


static void
iscsiAuthClientSetKeyValue(IscsiAuthKeyBlock * keyBlock,
    int keyType, const char *keyValue)
{
        unsigned int length;
        char *string;

        if (keyBlock->key[keyType].valueSet) {
                keyBlock->duplicateSet = TRUE;
                return;
        }

        keyBlock->key[keyType].valueSet = TRUE;

        if (!keyValue) {
                return;
        }

        if (iscsiAuthClientCheckString(keyValue,
            iscsiAuthStringMaxLength, &length)) {
                keyBlock->stringTooLong = TRUE;
                return;
        }

        length += 1;

        if ((keyBlock->blockLength + length) > iscsiAuthStringBlockMaxLength) {
                keyBlock->tooMuchData = TRUE;
                return;
        }

        string = &keyBlock->stringBlock[keyBlock->blockLength];

        if (iscsiAuthClientStringCopy(string, keyValue, length)) {
                keyBlock->tooMuchData = TRUE;
                return;
        }
        keyBlock->blockLength += length;

        keyBlock->key[keyType].string = string;
        keyBlock->key[keyType].present = TRUE;
}


static const char *
iscsiAuthClientGetKeyValue(IscsiAuthKeyBlock * keyBlock, int keyType)
{
        keyBlock->key[keyType].processed = TRUE;

        if (!keyBlock->key[keyType].present) {
                return (0);
        }

        return (keyBlock->key[keyType].string);
}


static void
iscsiAuthClientCheckKey(IscsiAuthClient * client,
    int keyType,
    int *negotiatedOption,
    unsigned int optionCount,
    int *optionList, const char *(*valueToText) (IscsiAuthClient *, int))
{
        const char *keyValue;
        int length;
        unsigned int i;

        keyValue = iscsiAuthClientGetKeyValue(&client->recvKeyBlock, keyType);
        if (!keyValue) {
                *negotiatedOption = iscsiAuthOptionNotPresent;
                return;
        }

        while (*keyValue != '\0') {

                length = 0;

                while (*keyValue != '\0' && *keyValue != ',') {
                        client->scratchKeyValue[length++] = *keyValue++;
                }

                if (*keyValue == ',')
                        keyValue++;
                client->scratchKeyValue[length++] = '\0';

                for (i = 0; i < optionCount; i++) {
                        const char *s = (*valueToText) (client, optionList[i]);

                        if (!s)
                                continue;

                        if (strcmp(client->scratchKeyValue, s) == 0) {
                                *negotiatedOption = optionList[i];
                                return;
                        }
                }
        }

        *negotiatedOption = iscsiAuthOptionReject;
}


static void
iscsiAuthClientSetKey(IscsiAuthClient * client,
    int keyType,
    unsigned int optionCount,
    int *optionList, const char *(*valueToText) (IscsiAuthClient *, int))
{
        unsigned int i;

        if (optionCount == 0) {
                /*
                 * No valid options to send, but we always want to
                 * send something.
                 */
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock, keyType,
                    client->noneOptionName);
                return;
        }

        if (optionCount == 1 && optionList[0] == iscsiAuthOptionNotPresent) {
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock, keyType, 0);
                return;
        }

        for (i = 0; i < optionCount; i++) {
                const char *s = (*valueToText) (client, optionList[i]);

                if (!s)
                        continue;

                if (i == 0) {
                        (void) iscsiAuthClientStringCopy(
                            client->scratchKeyValue,
                            s, iscsiAuthStringMaxLength);
                } else {
                        (void) iscsiAuthClientStringAppend(
                            client->scratchKeyValue,
                            ",", iscsiAuthStringMaxLength);
                        (void) iscsiAuthClientStringAppend(
                            client->scratchKeyValue,
                            s, iscsiAuthStringMaxLength);
                }
        }

        iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
            keyType, client->scratchKeyValue);
}


static void
iscsiAuthClientCheckAuthMethodKey(IscsiAuthClient * client)
{
        iscsiAuthClientCheckKey(client,
            iscsiAuthKeyTypeAuthMethod,
            &client->negotiatedAuthMethod,
            client->authMethodValidCount,
            client->authMethodValidList, iscsiAuthClientAuthMethodOptionToText);
}


static void
iscsiAuthClientSetAuthMethodKey(IscsiAuthClient * client,
    unsigned int authMethodCount, int *authMethodList)
{
        iscsiAuthClientSetKey(client, iscsiAuthKeyTypeAuthMethod,
            authMethodCount, authMethodList,
            iscsiAuthClientAuthMethodOptionToText);
}


static void
iscsiAuthClientCheckChapAlgorithmKey(IscsiAuthClient * client)
{
        const char *keyValue;
        int length;
        unsigned long number;
        unsigned int i;

        keyValue = iscsiAuthClientGetKeyValue(&client->recvKeyBlock,
            iscsiAuthKeyTypeChapAlgorithm);
        if (!keyValue) {
                client->negotiatedChapAlgorithm = iscsiAuthOptionNotPresent;
                return;
        }

        while (*keyValue != '\0') {
                length = 0;

                while (*keyValue != '\0' && *keyValue != ',') {
                        client->scratchKeyValue[length++] = *keyValue++;
                }

                if (*keyValue == ',')
                        keyValue++;
                client->scratchKeyValue[length++] = '\0';

                if (iscsiAuthClientTextToNumber(client->scratchKeyValue,
                    &number)) {
                        continue;
                }

                for (i = 0; i < client->chapAlgorithmCount; i++) {

                        if (number == (unsigned long)client->
                            chapAlgorithmList[i]) {
                                client->negotiatedChapAlgorithm = number;
                                return;
                        }
                }
        }

        client->negotiatedChapAlgorithm = iscsiAuthOptionReject;
}


static void
iscsiAuthClientSetChapAlgorithmKey(IscsiAuthClient * client,
    unsigned int chapAlgorithmCount, int *chapAlgorithmList)
{
        unsigned int i;

        if (chapAlgorithmCount == 0) {
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapAlgorithm, 0);
                return;
        }

        if (chapAlgorithmCount == 1 &&
            chapAlgorithmList[0] == iscsiAuthOptionNotPresent) {
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapAlgorithm, 0);
                return;
        }

        if (chapAlgorithmCount == 1 &&
            chapAlgorithmList[0] == iscsiAuthOptionReject) {
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapAlgorithm, client->rejectOptionName);
                return;
        }

        for (i = 0; i < chapAlgorithmCount; i++) {
                char s[20];

                iscsiAuthClientNumberToText(chapAlgorithmList[i],
                    s, sizeof (s));

                if (i == 0) {
                        (void) iscsiAuthClientStringCopy(
                            client->scratchKeyValue, s,
                            iscsiAuthStringMaxLength);
                } else {
                        (void) iscsiAuthClientStringAppend(
                            client->scratchKeyValue,
                            ",", iscsiAuthStringMaxLength);
                        (void) iscsiAuthClientStringAppend(
                            client->scratchKeyValue,
                            s, iscsiAuthStringMaxLength);
                }
        }

        iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
            iscsiAuthKeyTypeChapAlgorithm, client->scratchKeyValue);
}


static void
iscsiAuthClientNextPhase(IscsiAuthClient * client)
{
        switch (client->phase) {
        case iscsiAuthPhaseConfigure:
                client->phase = iscsiAuthPhaseNegotiate;
                break;

        case iscsiAuthPhaseNegotiate:
                client->phase = iscsiAuthPhaseAuthenticate;

                if (client->negotiatedAuthMethod ==
                    iscsiAuthOptionReject ||
                    client->negotiatedAuthMethod ==
                    iscsiAuthOptionNotPresent ||
                    client->negotiatedAuthMethod == iscsiAuthOptionNone) {

                        client->localState = iscsiAuthLocalStateDone;
                        client->remoteState = iscsiAuthRemoteStateDone;

                        if (client->authRemote) {
                                client->remoteAuthStatus = iscsiAuthStatusFail;
                                client->phase = iscsiAuthPhaseDone;
                        } else {
                                client->remoteAuthStatus = iscsiAuthStatusPass;
                        }

                        switch (client->negotiatedAuthMethod) {
                        case iscsiAuthOptionReject:
                                client->debugStatus =
                                    iscsiAuthDebugStatusAuthMethodReject;
                                break;

                        case iscsiAuthOptionNotPresent:
                                client->debugStatus =
                                    iscsiAuthDebugStatusAuthMethodNotPresent;
                                break;

                        case iscsiAuthOptionNone:
                                client->debugStatus =
                                    iscsiAuthDebugStatusAuthMethodNone;
                        }

                } else if (client->negotiatedAuthMethod ==
                    iscsiAuthMethodChap) {
                        client->localState = iscsiAuthLocalStateSendAlgorithm;
                        client->remoteState = iscsiAuthRemoteStateSendAlgorithm;
                } else {
                        client->localState = iscsiAuthLocalStateDone;
                        client->remoteState = iscsiAuthRemoteStateDone;
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->debugStatus = iscsiAuthDebugStatusAuthMethodBad;
                }

                break;

        case iscsiAuthPhaseAuthenticate:
                client->phase = iscsiAuthPhaseDone;
                break;

        case iscsiAuthPhaseDone:
        case iscsiAuthPhaseError:
        default:
                client->phase = iscsiAuthPhaseError;
        }
}


static void
iscsiAuthClientLocalAuthentication(IscsiAuthClient * client)
{
        unsigned int chapIdentifier;
        unsigned char responseData[iscsiAuthChapResponseLength];
        unsigned long number;
        int status;
        IscsiAuthDebugStatus debugStatus;
        const char *chapIdentifierKeyValue;
        const char *chapChallengeKeyValue;

        switch (client->localState) {
        case iscsiAuthLocalStateSendAlgorithm:
                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        iscsiAuthClientSetChapAlgorithmKey(
                            client, client->chapAlgorithmCount,
                            client->chapAlgorithmList);
                        client->localState = iscsiAuthLocalStateRecvAlgorithm;
                        break;
                }

                /* FALLTHRU */

        case iscsiAuthLocalStateRecvAlgorithm:
                iscsiAuthClientCheckChapAlgorithmKey(client);

                if (client->nodeType == iscsiAuthNodeTypeTarget) {

                        iscsiAuthClientSetChapAlgorithmKey(client, 1,
                            &client->negotiatedChapAlgorithm);
                }

                /*
                 * Make sure only supported CHAP algorithm is used.
                 */
                if (client->negotiatedChapAlgorithm ==
                    iscsiAuthOptionNotPresent) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapAlgorithmExpected;
                        break;

                } else if (client->negotiatedChapAlgorithm ==
                    iscsiAuthOptionReject) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapAlgorithmReject;
                        break;

                } else if (client->negotiatedChapAlgorithm !=
                    iscsiAuthChapAlgorithmMd5) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapAlgorithmBad;
                        break;
                }

                if (client->nodeType == iscsiAuthNodeTypeTarget) {

                        client->localState = iscsiAuthLocalStateRecvChallenge;
                        break;
                }

                /* FALLTHRU */

        case iscsiAuthLocalStateRecvChallenge:
                chapIdentifierKeyValue = iscsiAuthClientGetKeyValue(
                    &client->recvKeyBlock, iscsiAuthKeyTypeChapIdentifier);
                chapChallengeKeyValue = iscsiAuthClientGetKeyValue(
                    &client->recvKeyBlock, iscsiAuthKeyTypeChapChallenge);

                if (client->nodeType == iscsiAuthNodeTypeTarget) {
                        if (!chapIdentifierKeyValue && !chapChallengeKeyValue) {
                                client->localState = iscsiAuthLocalStateDone;
                                break;
                        }
                }

                if (!chapIdentifierKeyValue) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapIdentifierExpected;
                        break;
                }

                if (!chapChallengeKeyValue) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapChallengeExpected;
                        break;
                }

                status = iscsiAuthClientTextToNumber(
                    chapIdentifierKeyValue, &number);

                if (status || (255 < number)) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapIdentifierBad;
                        break;
                }
                chapIdentifier = number;

                if (client->recvChapChallengeStatus) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapChallengeBad;
                        break;
                }

                if (client->nodeType == iscsiAuthNodeTypeTarget &&
                    client->recvChapChallenge.length ==
                    client->sendChapChallenge.length &&
                    bcmp(client->recvChapChallenge.largeBinary,
                    client->sendChapChallenge.largeBinary,
                    client->sendChapChallenge.length) == 0) {

                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapChallengeReflected;
                        break;
                }

                debugStatus = iscsiAuthClientChapComputeResponse(client,
                    FALSE,
                    chapIdentifier,
                    client->recvChapChallenge.largeBinary,
                    client->recvChapChallenge.length, responseData);

                if (debugStatus != iscsiAuthDebugStatusNotSet) {
                        client->localState = iscsiAuthLocalStateError;
                        client->debugStatus = debugStatus;
                        break;
                }

                (void) iscsiAuthClientDataToText(client->base64,
                    responseData, iscsiAuthChapResponseLength,
                    client->scratchKeyValue, iscsiAuthStringMaxLength);
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapResponse, client->scratchKeyValue);

                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapUsername, client->username);

                client->localState = iscsiAuthLocalStateDone;
                break;

        case iscsiAuthLocalStateDone:
                break;

        case iscsiAuthLocalStateError:
        default:
                client->phase = iscsiAuthPhaseError;
        }
}


static void
iscsiAuthClientRemoteAuthentication(IscsiAuthClient * client)
{
        unsigned char idData[1];
        unsigned char responseData[iscsiAuthStringMaxLength];
        unsigned int responseLength = iscsiAuthStringMaxLength;
        unsigned char myResponseData[iscsiAuthChapResponseLength];
        int status;
        IscsiAuthDebugStatus debugStatus;
        const char *chapResponseKeyValue;
        const char *chapUsernameKeyValue;

        switch (client->remoteState) {
        case iscsiAuthRemoteStateSendAlgorithm:
                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        client->remoteState = iscsiAuthRemoteStateSendChallenge;
                        break;
                }

                /* FALLTHRU */

        case iscsiAuthRemoteStateSendChallenge:
                if (!client->authRemote) {
                        client->remoteAuthStatus = iscsiAuthStatusPass;
                        client->debugStatus =
                            iscsiAuthDebugStatusAuthRemoteFalse;
                        client->remoteState = iscsiAuthRemoteStateDone;
                        break;
                }

                iscsiAuthRandomSetData(idData, 1);
                client->sendChapIdentifier = idData[0];

                iscsiAuthClientNumberToText(client->sendChapIdentifier,
                    client->scratchKeyValue, iscsiAuthStringMaxLength);
                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapIdentifier, client->scratchKeyValue);

                client->sendChapChallenge.length = client->chapChallengeLength;
                iscsiAuthRandomSetData(client->sendChapChallenge.largeBinary,
                    client->sendChapChallenge.length);

                iscsiAuthClientSetKeyValue(&client->sendKeyBlock,
                    iscsiAuthKeyTypeChapChallenge, "");

                client->remoteState = iscsiAuthRemoteStateRecvResponse;
                break;

        case iscsiAuthRemoteStateRecvResponse:
                chapResponseKeyValue = iscsiAuthClientGetKeyValue(
                    &client->recvKeyBlock, iscsiAuthKeyTypeChapResponse);

                chapUsernameKeyValue = iscsiAuthClientGetKeyValue(
                    &client->recvKeyBlock, iscsiAuthKeyTypeChapUsername);

                if (!chapResponseKeyValue) {
                        client->remoteState = iscsiAuthRemoteStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapResponseExpected;
                        break;
                }

                if (!chapUsernameKeyValue) {
                        client->remoteState = iscsiAuthRemoteStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapUsernameExpected;
                        break;
                }

                status = iscsiAuthClientTextToData(chapResponseKeyValue,
                    responseData, &responseLength);

                if (status) {
                        client->remoteState = iscsiAuthRemoteStateError;
                        client->debugStatus =
                            iscsiAuthDebugStatusChapResponseBad;
                        break;
                }

                if (responseLength == iscsiAuthChapResponseLength) {
                        debugStatus = iscsiAuthClientChapComputeResponse(
                            client, TRUE, client->sendChapIdentifier,
                            client->sendChapChallenge.largeBinary,
                            client->sendChapChallenge.length, myResponseData);

                        /*
                         * Check if the same CHAP secret is being used for
                         * authentication in both directions.
                         */
                        if (debugStatus == iscsiAuthDebugStatusNotSet &&
                            bcmp(myResponseData, responseData,
                            iscsiAuthChapResponseLength) == 0) {

                                client->remoteState =
                                    iscsiAuthRemoteStateError;
                                client->debugStatus =
                                    iscsiAuthDebugStatusPasswordIdentical;
                                break;
                        }
                }

                (void) iscsiAuthClientStringCopy(client->chapUsername,
                    chapUsernameKeyValue, iscsiAuthStringMaxLength);

                /* To verify the target's response. */
                status = iscsiAuthClientChapAuthRequest(
                    client, client->chapUsername, client->sendChapIdentifier,
                    client->sendChapChallenge.largeBinary,
                    client->sendChapChallenge.length, responseData,
                    responseLength);

                if (status == iscsiAuthStatusInProgress) {
                        iscsiAuthClientGlobalStats.requestSent++;
                        client->remoteState = iscsiAuthRemoteStateAuthRequest;
                        break;
                }

                client->remoteAuthStatus = (IscsiAuthStatus) status;
                client->authResponseFlag = TRUE;

                /* FALLTHRU */

        case iscsiAuthRemoteStateAuthRequest:
                /*
                 * client->remoteAuthStatus already set
                 */
                if (client->authServerErrorFlag) {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->debugStatus =
                            iscsiAuthDebugStatusAuthServerError;
                } else if (client->remoteAuthStatus == iscsiAuthStatusPass) {
                        client->debugStatus = iscsiAuthDebugStatusAuthPass;
                } else if (client->remoteAuthStatus == iscsiAuthStatusFail) {
                        client->debugStatus = iscsiAuthDebugStatusAuthFail;
                } else {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->debugStatus = iscsiAuthDebugStatusAuthStatusBad;
                }
                client->remoteState = iscsiAuthRemoteStateDone;

                /* FALLTHRU */

        case iscsiAuthRemoteStateDone:
                break;

        case iscsiAuthRemoteStateError:
        default:
                client->phase = iscsiAuthPhaseError;
        }
}


static void
iscsiAuthClientHandshake(IscsiAuthClient * client)
{
        if (client->phase == iscsiAuthPhaseDone) {
                /*
                 * Should only happen if authentication
                 * protocol error occured.
                 */
                return;
        }

        if (client->remoteState == iscsiAuthRemoteStateAuthRequest) {
                /*
                 * Defer until authentication response received
                 * from internal authentication service.
                 */
                return;
        }

        if (client->nodeType == iscsiAuthNodeTypeInitiator) {

                /*
                 * Target should only have set T bit on response if
                 * initiator set it on previous message.
                 */
                if (client->recvKeyBlock.transitBit &&
                    client->transitBitSentFlag == 0) {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->phase = iscsiAuthPhaseDone;
                        client->debugStatus =
                            iscsiAuthDebugStatusTbitSetIllegal;
                        return;
                }
        }

        if (client->phase == iscsiAuthPhaseNegotiate) {
                /*
                 * Should only happen if waiting for peer
                 * to send AuthMethod key or set Transit Bit.
                 */
                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        client->sendKeyBlock.transitBit = TRUE;
                }
                return;
        }

        if (client->remoteState == iscsiAuthRemoteStateRecvResponse ||
            client->remoteState == iscsiAuthRemoteStateDone) {

                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        if (client->recvKeyBlock.transitBit) {
                                if (client->remoteState !=
                                    iscsiAuthRemoteStateDone) {
                                        goto recvTransitBitError;
                                }
                                iscsiAuthClientNextPhase(client);
                        } else {
                                client->sendKeyBlock.transitBit = TRUE;
                        }
                } else {
                        if (client->remoteState == iscsiAuthRemoteStateDone &&
                            client->remoteAuthStatus != iscsiAuthStatusPass) {

                                /*
                                 * Authentication failed, don't
                                 * do T bit handshake.
                                 */
                                iscsiAuthClientNextPhase(client);
                        } else {

                                /*
                                 * Target can only set T bit on response if
                                 * initiator set it on current message.
                                 */
                                if (client->recvKeyBlock.transitBit) {
                                        client->sendKeyBlock.transitBit = TRUE;
                                        iscsiAuthClientNextPhase(client);
                                }
                        }
                }
        } else {
                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        if (client->recvKeyBlock.transitBit) {
                                goto recvTransitBitError;
                        }
                }
        }

        return;

recvTransitBitError:
        /*
         * Target set T bit on response but
         * initiator was not done with authentication.
         */
        client->remoteAuthStatus = iscsiAuthStatusFail;
        client->phase = iscsiAuthPhaseDone;
        client->debugStatus = iscsiAuthDebugStatusTbitSetPremature;
}


static int
iscsiAuthClientRecvEndStatus(IscsiAuthClient * client)
{
        int authStatus;
        int keyType;

        if (client->phase == iscsiAuthPhaseError) {
                return (iscsiAuthStatusError);
        }

        if (client->phase == iscsiAuthPhaseDone) {

                /*
                 * Perform sanity check against configured parameters.
                 */

                if (client->authRemote && !client->authResponseFlag &&
                    client->remoteAuthStatus == iscsiAuthStatusPass) {

                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->debugStatus =
                            iscsiAuthDebugStatusAuthPassNotValid;
                }

                authStatus = client->remoteAuthStatus;
        } else if (client->remoteState == iscsiAuthRemoteStateAuthRequest) {
                authStatus = iscsiAuthStatusInProgress;
        } else {
                authStatus = iscsiAuthStatusContinue;
        }

        if (authStatus != iscsiAuthStatusInProgress) {
                client->recvInProgressFlag = FALSE;
        }

        if (authStatus == iscsiAuthStatusContinue ||
            authStatus == iscsiAuthStatusPass) {
                if (client->sendKeyBlock.duplicateSet) {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->phase = iscsiAuthPhaseDone;
                        client->debugStatus =
                            iscsiAuthDebugStatusSendDuplicateSetKeyValue;
                        authStatus = iscsiAuthStatusFail;
                } else if (client->sendKeyBlock.stringTooLong) {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->phase = iscsiAuthPhaseDone;
                        client->debugStatus =
                            iscsiAuthDebugStatusSendStringTooLong;
                        authStatus = iscsiAuthStatusFail;
                } else if (client->sendKeyBlock.tooMuchData) {
                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->phase = iscsiAuthPhaseDone;
                        client->debugStatus =
                            iscsiAuthDebugStatusSendTooMuchData;
                        authStatus = iscsiAuthStatusFail;
                } else {
                        /*
                         * Check that all incoming keys have been processed.
                         */
                        for (keyType = iscsiAuthKeyTypeFirst;
                            keyType < iscsiAuthKeyTypeMaxCount; keyType++) {
                                if (client->recvKeyBlock.key[keyType].present &&
                                    client->recvKeyBlock.key[keyType].
                                    processed == 0) {
                                        break;
                                }
                        }

                        if (keyType < iscsiAuthKeyTypeMaxCount) {
                                client->remoteAuthStatus = iscsiAuthStatusFail;
                                client->phase = iscsiAuthPhaseDone;
                                client->debugStatus =
                                    iscsiAuthDebugStatusUnexpectedKeyPresent;
                                authStatus = iscsiAuthStatusFail;
                        }
                }
        }

        if (authStatus != iscsiAuthStatusPass &&
            authStatus != iscsiAuthStatusContinue &&
            authStatus != iscsiAuthStatusInProgress) {
                int authMethodKeyPresent = FALSE;
                int chapAlgorithmKeyPresent = FALSE;

                /*
                 * Suppress send keys on error, except
                 * for AuthMethod and CHAP_A.
                 */
                if (client->nodeType == iscsiAuthNodeTypeTarget) {
                        if (iscsiAuthClientGetKeyValue(&client->sendKeyBlock,
                            iscsiAuthKeyTypeAuthMethod)) {
                                authMethodKeyPresent = TRUE;
                        } else if (iscsiAuthClientGetKeyValue(
                            &client->sendKeyBlock,
                            iscsiAuthKeyTypeChapAlgorithm)) {
                                chapAlgorithmKeyPresent = TRUE;
                        }
                }

                iscsiAuthClientInitKeyBlock(&client->sendKeyBlock);

                if (client->nodeType == iscsiAuthNodeTypeTarget) {
                        if (authMethodKeyPresent &&
                            client->negotiatedAuthMethod ==
                            iscsiAuthOptionReject) {
                                iscsiAuthClientSetKeyValue(
                                    &client->sendKeyBlock,
                                    iscsiAuthKeyTypeAuthMethod,
                                    client->rejectOptionName);
                        } else if (chapAlgorithmKeyPresent &&
                            client->negotiatedChapAlgorithm ==
                            iscsiAuthOptionReject) {
                                iscsiAuthClientSetKeyValue(
                                    &client->sendKeyBlock,
                                    iscsiAuthKeyTypeChapAlgorithm,
                                    client->rejectOptionName);
                        }
                }
        }

        return (authStatus);
}


int
iscsiAuthClientRecvBegin(IscsiAuthClient * client)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase == iscsiAuthPhaseError) {
                return (iscsiAuthStatusError);
        }

        if (client->phase == iscsiAuthPhaseDone) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (client->recvInProgressFlag) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->recvInProgressFlag = TRUE;

        if (client->phase == iscsiAuthPhaseConfigure) {
                iscsiAuthClientNextPhase(client);
        }

        client->transitBitSentFlag = client->sendKeyBlock.transitBit;

        iscsiAuthClientInitKeyBlock(&client->recvKeyBlock);
        iscsiAuthClientInitKeyBlock(&client->sendKeyBlock);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientRecvEnd(IscsiAuthClient * client,
    IscsiAuthClientCallback * callback, void *userHandle, void *messageHandle)
{
        int nextPhaseFlag = FALSE;

        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase == iscsiAuthPhaseError) {
                return (iscsiAuthStatusError);
        }

        if (!callback || !client->recvInProgressFlag) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (client->recvEndCount > iscsiAuthRecvEndMaxCount) {
                client->remoteAuthStatus = iscsiAuthStatusFail;
                client->phase = iscsiAuthPhaseDone;
                client->debugStatus =
                    iscsiAuthDebugStatusRecvMessageCountLimit;
        } else if (client->recvKeyBlock.duplicateSet) {
                client->remoteAuthStatus = iscsiAuthStatusFail;
                client->phase = iscsiAuthPhaseDone;
                client->debugStatus =
                    iscsiAuthDebugStatusRecvDuplicateSetKeyValue;
        } else if (client->recvKeyBlock.stringTooLong) {
                client->remoteAuthStatus = iscsiAuthStatusFail;
                client->phase = iscsiAuthPhaseDone;
                client->debugStatus = iscsiAuthDebugStatusRecvStringTooLong;
        } else if (client->recvKeyBlock.tooMuchData) {
                client->remoteAuthStatus = iscsiAuthStatusFail;
                client->phase = iscsiAuthPhaseDone;
                client->debugStatus = iscsiAuthDebugStatusRecvTooMuchData;
        }

        client->recvEndCount++;

        client->callback = callback;
        client->userHandle = userHandle;
        client->messageHandle = messageHandle;

        switch (client->phase) {
        case iscsiAuthPhaseNegotiate:
                iscsiAuthClientCheckAuthMethodKey(client);

                if (client->authMethodValidNegRole ==
                    iscsiAuthNegRoleResponder) {
                        if (client->negotiatedAuthMethod ==
                            iscsiAuthOptionNotPresent) {
                                if (client->authRemote ||
                                    client->recvKeyBlock.transitBit == 0) {
                                        /*
                                         * No AuthMethod key from peer
                                         * on first message, try moving
                                         * the process along by sending
                                         * the AuthMethod key.
                                         */

                                        client->authMethodValidNegRole =
                                            iscsiAuthNegRoleOriginator;

                                        iscsiAuthClientSetAuthMethodKey(client,
                                            client->authMethodValidCount,
                                            client->authMethodValidList);
                                        break;
                                }

                                /*
                                 * Special case if peer sent no
                                 * AuthMethod key, but did set Transit
                                 * Bit, allowing this side to do a
                                 * null authentication, and compelete
                                 * the iSCSI security phase without
                                 * either side sending the AuthMethod
                                 * key.
                                 */
                        } else {
                                /*
                                 * Send response to AuthMethod key.
                                 */

                                iscsiAuthClientSetAuthMethodKey(client, 1,
                                    &client->negotiatedAuthMethod);
                        }

                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                iscsiAuthClientNextPhase(client);
                        } else {
                                nextPhaseFlag = TRUE;
                        }

                } else {
                        if (client->negotiatedAuthMethod ==
                            iscsiAuthOptionNotPresent) {
                                client->remoteAuthStatus = iscsiAuthStatusFail;
                                client->phase = iscsiAuthPhaseDone;
                                client->debugStatus =
                                    iscsiAuthDebugStatusAuthMethodExpected;
                                break;
                        }

                        iscsiAuthClientNextPhase(client);
                }
                break;

        case iscsiAuthPhaseAuthenticate:
        case iscsiAuthPhaseDone:
                break;

        default:
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        switch (client->phase) {
        case iscsiAuthPhaseNegotiate:
                if (nextPhaseFlag) {
                        iscsiAuthClientNextPhase(client);
                }
                break;

        case iscsiAuthPhaseAuthenticate:
                /*
                 * Must call iscsiAuthClientLocalAuthentication()
                 * before iscsiAuthClientRemoteAuthentication()
                 * to insure processing of the CHAP algorithm key,
                 * and to avoid leaving an in progress request to the
                 * authentication service.
                 */
                iscsiAuthClientLocalAuthentication(client);

                if (client->localState != iscsiAuthLocalStateError) {
                        iscsiAuthClientRemoteAuthentication(client);
                }

                if (client->localState == iscsiAuthLocalStateError ||
                    client->remoteState == iscsiAuthRemoteStateError) {

                        client->remoteAuthStatus = iscsiAuthStatusFail;
                        client->phase = iscsiAuthPhaseDone;
                        /*
                         * client->debugStatus should already be set.
                         */
                }
                break;

        case iscsiAuthPhaseDone:
                break;

        default:
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        iscsiAuthClientHandshake(client);

        return (iscsiAuthClientRecvEndStatus(client));
}


void
iscsiAuthClientAuthResponse(IscsiAuthClient * client, int authStatus)
{
        iscsiAuthClientGlobalStats.responseReceived++;

        if (!client || client->signature != iscsiAuthClientSignature) {
                return;
        }

        if (!client->recvInProgressFlag ||
            client->phase != iscsiAuthPhaseAuthenticate ||
            client->remoteState != iscsiAuthRemoteStateAuthRequest) {

                client->phase = iscsiAuthPhaseError;
                return;
        }

        client->remoteAuthStatus = (IscsiAuthStatus) authStatus;
        client->authResponseFlag = TRUE;

        iscsiAuthClientRemoteAuthentication(client);

        iscsiAuthClientHandshake(client);

        authStatus = iscsiAuthClientRecvEndStatus(client);

        client->callback(client->userHandle, client->messageHandle, authStatus);
}


const char *
iscsiAuthClientGetKeyName(int keyType)
{
        if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) {
                return (0);
        }
        return (iscsiAuthClientKeyInfo[keyType].name);
}


int
iscsiAuthClientGetNextKeyType(int *pKeyType)
{
        int keyType = *pKeyType;

        if (keyType >= iscsiAuthKeyTypeLast) {
                return (iscsiAuthStatusError);
        }

        if (keyType < iscsiAuthKeyTypeFirst) {
                keyType = iscsiAuthKeyTypeFirst;
        } else {
                keyType++;
        }

        *pKeyType = keyType;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientKeyNameToKeyType(const char *keyName)
{
        int keyType = iscsiAuthKeyTypeNone;

        while (iscsiAuthClientGetNextKeyType(&keyType) ==
            iscsiAuthStatusNoError) {
                const char *keyName2 = iscsiAuthClientGetKeyName(keyType);

                if (!keyName2) {
                        return (iscsiAuthKeyTypeNone);
                }

                if (strcmp(keyName, keyName2) == 0) {
                        return (keyType);
                }
        }

        return (iscsiAuthKeyTypeNone);
}


int
iscsiAuthClientRecvKeyValue(IscsiAuthClient * client, int keyType,
    const char *userKeyValue)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (keyType == iscsiAuthKeyTypeChapChallenge) {
                client->recvChapChallenge.length =
                    iscsiAuthLargeBinaryMaxLength;
                client->recvChapChallengeStatus =
                    iscsiAuthClientTextToData(userKeyValue,
                    client->recvChapChallenge.largeBinary,
                    &client->recvChapChallenge.length);
                userKeyValue = "";
        }

        iscsiAuthClientSetKeyValue(&client->recvKeyBlock,
            keyType, userKeyValue);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSendKeyValue(IscsiAuthClient * client, int keyType,
    int *keyPresent, char *userKeyValue, unsigned int maxLength)
{
        const char *keyValue;

        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure &&
            client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate &&
            client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (keyType < iscsiAuthKeyTypeFirst || keyType > iscsiAuthKeyTypeLast) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        keyValue = iscsiAuthClientGetKeyValue(&client->sendKeyBlock, keyType);
        if (keyValue) {
                if (keyType == iscsiAuthKeyTypeChapChallenge) {
                        if (iscsiAuthClientDataToText(client->base64,
                            client->sendChapChallenge.largeBinary,
                            client->sendChapChallenge.length,
                            userKeyValue, maxLength)) {
                                client->phase = iscsiAuthPhaseError;
                                return (iscsiAuthStatusError);
                        }
                } else {
                        if (iscsiAuthClientStringCopy(userKeyValue,
                            keyValue, maxLength)) {
                                client->phase = iscsiAuthPhaseError;
                                return (iscsiAuthStatusError);
                        }
                }
                *keyPresent = TRUE;
        } else {
                *keyPresent = FALSE;
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientRecvTransitBit(IscsiAuthClient * client, int value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (value) {
                client->recvKeyBlock.transitBit = TRUE;
        } else {
                client->recvKeyBlock.transitBit = FALSE;
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSendTransitBit(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure &&
            client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate &&
            client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        *value = client->sendKeyBlock.transitBit;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientInit(int nodeType, int bufferDescCount,
    IscsiAuthBufferDesc * bufferDesc)
{
        IscsiAuthClient *client;
        IscsiAuthStringBlock *recvStringBlock;
        IscsiAuthStringBlock *sendStringBlock;
        IscsiAuthLargeBinary *recvChapChallenge;
        IscsiAuthLargeBinary *sendChapChallenge;
        int valueList[2];

        if (bufferDescCount != 5 ||
            bufferDesc == 0) {
                return (iscsiAuthStatusError);
        }

        if (!bufferDesc[0].address ||
            bufferDesc[0].length != sizeof (*client)) {
                return (iscsiAuthStatusError);
        }
        client = (IscsiAuthClient *) bufferDesc[0].address;

        if (bufferDesc[1].address == 0 ||
            bufferDesc[1].length != sizeof (*recvStringBlock)) {
                return (iscsiAuthStatusError);
        }
        recvStringBlock = (IscsiAuthStringBlock *) bufferDesc[1].address;

        if (bufferDesc[2].address == 0 ||
            bufferDesc[2].length != sizeof (*sendStringBlock)) {
                return (iscsiAuthStatusError);
        }
        sendStringBlock = (IscsiAuthStringBlock *) bufferDesc[2].address;

        if (bufferDesc[3].address == 0 ||
            bufferDesc[3].length != sizeof (*recvChapChallenge)) {
                return (iscsiAuthStatusError);
        }
        recvChapChallenge = (IscsiAuthLargeBinary *) bufferDesc[3].address;

        if (bufferDesc[4].address == 0 ||
            bufferDesc[4].length != sizeof (*sendChapChallenge)) {
                return (iscsiAuthStatusError);
        }
        sendChapChallenge = (IscsiAuthLargeBinary *) bufferDesc[4].address;

        bzero(client, sizeof (*client));
        bzero(recvStringBlock, sizeof (*recvStringBlock));
        bzero(sendStringBlock, sizeof (*sendStringBlock));
        bzero(recvChapChallenge, sizeof (*recvChapChallenge));
        bzero(sendChapChallenge, sizeof (*sendChapChallenge));

        client->recvKeyBlock.stringBlock = recvStringBlock->stringBlock;
        client->sendKeyBlock.stringBlock = sendStringBlock->stringBlock;
        client->recvChapChallenge.largeBinary = recvChapChallenge->largeBinary;
        client->sendChapChallenge.largeBinary = sendChapChallenge->largeBinary;

        if (iscsiAuthClientCheckNodeType(nodeType)) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->signature = iscsiAuthClientSignature;
        client->nodeType = (IscsiAuthNodeType) nodeType;
        /* Assume bi-directional authentication enabled. */
        client->authRemote = TRUE;
        client->passwordPresent = FALSE;
        client->version = iscsiAuthVersionRfc;
        client->chapChallengeLength = iscsiAuthChapResponseLength;
        client->ipSec = TRUE;
        client->base64 = FALSE;

        client->phase = iscsiAuthPhaseConfigure;
        client->negotiatedAuthMethod = iscsiAuthOptionNotPresent;
        client->negotiatedChapAlgorithm = iscsiAuthOptionNotPresent;

        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                client->authMethodNegRole = iscsiAuthNegRoleOriginator;
        } else {
                /*
                 * Initial value ignored for Target.
                 */
                client->authMethodNegRole = iscsiAuthNegRoleResponder;
        }

        /* All supported authentication methods */
        valueList[0] = iscsiAuthMethodChap;
        valueList[1] = iscsiAuthOptionNone;

        /*
         * Must call after setting authRemote, password,
         * version and authMethodNegRole
         */
        if (iscsiAuthClientSetAuthMethodList(client, 2, valueList) !=
            iscsiAuthStatusNoError) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        valueList[0] = iscsiAuthChapAlgorithmMd5;

        if (iscsiAuthClientSetChapAlgorithmList(client, 1, valueList) !=
            iscsiAuthStatusNoError) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientFinish(IscsiAuthClient * client)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        iscsiAuthClientChapAuthCancel(client);

        bzero(client, sizeof (*client));

        return (iscsiAuthStatusNoError);
}


static int
iscsiAuthClientSetOptionList(IscsiAuthClient * client,
    unsigned int optionCount,
    const int *optionList,
    unsigned int *clientOptionCount,
    int *clientOptionList,
    unsigned int optionMaxCount,
    int (*checkOption) (int),
    int (*checkList) (unsigned int optionCount, const int *optionList))
{
        unsigned int i;
        unsigned int j;

        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            optionCount > optionMaxCount) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        for (i = 0; i < optionCount; i++) {
                if ((*checkOption) (optionList[i])) {
                        client->phase = iscsiAuthPhaseError;
                        return (iscsiAuthStatusError);
                }
        }

        /*
         * Check for duplicate entries.
         */
        for (i = 0; i < optionCount; i++) {
                for (j = 0; j < optionCount; j++) {
                        if (j == i)
                                continue;
                        if (optionList[i] == optionList[j]) {
                                client->phase = iscsiAuthPhaseError;
                                return (iscsiAuthStatusError);
                        }
                }
        }

        /*
         * Check for key specific constraints.
         */
        if (checkList) {
                if ((*checkList) (optionCount, optionList)) {
                        client->phase = iscsiAuthPhaseError;
                        return (iscsiAuthStatusError);
                }
        }

        for (i = 0; i < optionCount; i++) {
                clientOptionList[i] = optionList[i];
        }

        *clientOptionCount = optionCount;

        return (iscsiAuthStatusNoError);
}


static void
iscsiAuthClientSetAuthMethodValid(IscsiAuthClient * client)
{
        static const char rejectOptionNameDraft8[] = "reject";
        static const char rejectOptionNameRfc[] = "Reject";
        static const char noneOptionNameDraft8[] = "none";
        static const char noneOptionNameRfc[] = "None";
        unsigned int i;
        unsigned int j = 0;
        int option = 0;

        if (client->version == iscsiAuthVersionDraft8) {
                client->rejectOptionName = rejectOptionNameDraft8;
                client->noneOptionName = noneOptionNameDraft8;
        } else {
                client->rejectOptionName = rejectOptionNameRfc;
                client->noneOptionName = noneOptionNameRfc;
        }

        /*
         * Following checks may need to be revised if
         * authentication options other than CHAP and none
         * are supported.
         */

        if (client->nodeType == iscsiAuthNodeTypeInitiator) {

                if (client->authRemote) {
                        /*
                         * If initiator doing authentication,
                         * don't offer authentication option none.
                         */
                        option = 1;
                } else if (!client->passwordPresent) {
                        /*
                         * If initiator password not set,
                         * only offer authentication option none.
                         */
                        option = 2;
                }
        }

        if (client->nodeType == iscsiAuthNodeTypeTarget) {

                if (client->authRemote) {
                        /*
                         * If target doing authentication,
                         * don't accept authentication option none.
                         */
                        option = 1;
                } else {
                        /*
                         * If target not doing authentication,
                         * only accept authentication option none.
                         */
                        option = 2;
                }
        }

        for (i = 0; i < client->authMethodCount; i++) {

                if (option == 1) {
                        if (client->authMethodList[i] == iscsiAuthOptionNone) {
                                continue;
                        }
                } else if (option == 2) {
                        if (client->authMethodList[i] != iscsiAuthOptionNone) {
                                continue;
                        }
                }

                client->authMethodValidList[j++] = client->authMethodList[i];
        }

        client->authMethodValidCount = j;

        iscsiAuthClientInitKeyBlock(&client->sendKeyBlock);

        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                if (client->authRemote) {
                        /*
                         * Initiator wants to authenticate target,
                         * always send AuthMethod key.
                         */
                        client->sendKeyBlock.transitBit = FALSE;
                        client->authMethodValidNegRole =
                            iscsiAuthNegRoleOriginator;
                } else {
                        client->sendKeyBlock.transitBit = TRUE;
                        client->authMethodValidNegRole =
                            client->authMethodNegRole;
                }
        } else {
                client->sendKeyBlock.transitBit = FALSE;
                client->authMethodValidNegRole = iscsiAuthNegRoleResponder;
        }

        if (client->authMethodValidNegRole == iscsiAuthNegRoleOriginator) {
                iscsiAuthClientSetAuthMethodKey(client,
                    client->authMethodValidCount,
                    client->authMethodValidList);
        } else {
                int value = iscsiAuthOptionNotPresent;
                iscsiAuthClientSetAuthMethodKey(client, 1, &value);
        }
}


static int
iscsiAuthClientCheckAuthMethodList(unsigned int optionCount,
    const int *optionList)
{
        unsigned int i;

        if (!optionList || optionCount < 2) {
                return (TRUE);
        }

        if (optionList[optionCount - 1] != iscsiAuthOptionNone) {
                return (TRUE);
        }

        for (i = 0; i < (optionCount - 1); i++) {
                if (optionList[i] != iscsiAuthOptionNone) {
                        return (FALSE);
                }
        }

        return (FALSE);
}


int
iscsiAuthClientSetAuthMethodList(IscsiAuthClient * client,
    unsigned int optionCount, const int *optionList)
{
        int status;

        status = iscsiAuthClientSetOptionList(
            client, optionCount, optionList, &client->authMethodCount,
            client->authMethodList, iscsiAuthMethodMaxCount,
            iscsiAuthClientCheckAuthMethodOption,
            iscsiAuthClientCheckAuthMethodList);

        if (status != iscsiAuthStatusNoError) {
                return (status);
        }

        /*
         * Setting authMethod affects authMethodValid.
         */
        iscsiAuthClientSetAuthMethodValid(client);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetAuthMethodNegRole(IscsiAuthClient * client, int negRole)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            iscsiAuthClientCheckNegRole(negRole) ||
            client->nodeType != iscsiAuthNodeTypeInitiator) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->authMethodNegRole = (IscsiAuthNegRole) negRole;

        /*
         * Setting negRole affects authMethodValid.
         */
        iscsiAuthClientSetAuthMethodValid(client);

        return (iscsiAuthStatusNoError);
}


static int
iscsiAuthClientCheckChapAlgorithmList(unsigned int optionCount,
    const int *optionList)
{
        if (!optionList || optionCount < 1) {
                return (TRUE);
        }

        return (FALSE);
}


int
iscsiAuthClientSetChapAlgorithmList(IscsiAuthClient * client,
    unsigned int optionCount, const int *optionList)
{
        return (iscsiAuthClientSetOptionList(client,
            optionCount,
            optionList,
            &client->chapAlgorithmCount,
            client->chapAlgorithmList,
            iscsiAuthChapAlgorithmMaxCount,
            iscsiAuthClientCheckChapAlgorithmOption,
            iscsiAuthClientCheckChapAlgorithmList));
}


int
iscsiAuthClientSetUsername(IscsiAuthClient * client, const char *username)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            iscsiAuthClientCheckString(username, iscsiAuthStringMaxLength, 0)) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (iscsiAuthClientStringCopy(client->username, username,
            iscsiAuthStringMaxLength)) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetPassword(IscsiAuthClient * client,
    const unsigned char *passwordData, unsigned int passwordLength)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            passwordLength > iscsiAuthStringMaxLength) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        bcopy(passwordData, client->passwordData, passwordLength);
        client->passwordLength = passwordLength;
        if (client->passwordLength > 0) {
                client->passwordPresent = TRUE;
        } else {
                client->passwordPresent = FALSE;
        }

        /*
         * Setting password may affect authMethodValid.
         */
        iscsiAuthClientSetAuthMethodValid(client);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetAuthRemote(IscsiAuthClient * client, int authRemote)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->authRemote = authRemote;

        /*
         * Setting authRemote may affect authMethodValid.
         */
        iscsiAuthClientSetAuthMethodValid(client);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetGlueHandle(IscsiAuthClient * client, void *glueHandle)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure &&
            client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->glueHandle = glueHandle;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetMethodListName(IscsiAuthClient *client,
    const char *methodListName)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            iscsiAuthClientCheckString(methodListName,
            iscsiAuthStringMaxLength, 0)) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (iscsiAuthClientStringCopy(client->methodListName, methodListName,
            iscsiAuthStringMaxLength)) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetVersion(IscsiAuthClient * client, int version)
{
        if (client == 0 ||
            client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            iscsiAuthClientCheckVersion(version)) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->version = (IscsiAuthVersion) version;

        iscsiAuthClientSetAuthMethodValid(client);

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetIpSec(IscsiAuthClient * client, int ipSec)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->ipSec = ipSec;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetBase64(IscsiAuthClient * client, int base64)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->base64 = base64;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSetChapChallengeLength(IscsiAuthClient * client,
    unsigned int chapChallengeLength)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure ||
            chapChallengeLength < iscsiAuthChapResponseLength ||
            chapChallengeLength > iscsiAuthLargeBinaryMaxLength) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        client->chapChallengeLength = chapChallengeLength;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientCheckPasswordNeeded(IscsiAuthClient *client, int *passwordNeeded)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                if (client->authRemote && !client->passwordPresent) {
                        *passwordNeeded = TRUE;
                } else {
                        *passwordNeeded = FALSE;
                }
        } else {
                *passwordNeeded = FALSE;
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientGetAuthPhase(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        *value = client->phase;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientGetAuthStatus(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        *value = client->remoteAuthStatus;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientAuthStatusPass(int authStatus)
{
        if (authStatus == iscsiAuthStatusPass) {
                return (TRUE);
        }

        return (FALSE);
}


int
iscsiAuthClientGetAuthMethod(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone &&
            client->phase != iscsiAuthPhaseAuthenticate) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        *value = client->negotiatedAuthMethod;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientGetChapAlgorithm(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        *value = client->negotiatedChapAlgorithm;

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientGetChapUsername(IscsiAuthClient * client,
    char *value, unsigned int maxLength)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (iscsiAuthClientStringCopy(value, client->chapUsername, maxLength)) {
                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientSendStatusCode(IscsiAuthClient * client, int *statusCode)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseConfigure &&
            client->phase != iscsiAuthPhaseNegotiate &&
            client->phase != iscsiAuthPhaseAuthenticate &&
            client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone) {
                *statusCode = 0x0000;
                return (iscsiAuthStatusNoError);
        }

        switch (client->remoteAuthStatus) {
        case iscsiAuthStatusPass:
                *statusCode = 0x0000;   /* no error */
                break;

        case iscsiAuthStatusFail:
                switch (client->debugStatus) {
                case iscsiAuthDebugStatusAuthFail:
                        /*
                         * Authentication error with peer.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0300;
                                /*
                                 * iSCSI Target error
                                 */
                        } else {
                                *statusCode = 0x0201;
                                /*
                                 * iSCSI Initiator error
                                 */
                        }
                        break;

                case iscsiAuthDebugStatusAuthMethodExpected:
                case iscsiAuthDebugStatusChapAlgorithmExpected:
                case iscsiAuthDebugStatusChapIdentifierExpected:
                case iscsiAuthDebugStatusChapChallengeExpected:
                case iscsiAuthDebugStatusChapResponseExpected:
                case iscsiAuthDebugStatusChapUsernameExpected:
                        /*
                         * Missing parameter with peer.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0300;
                                /*
                                 * iSCSI Target error
                                 */
                        } else {
                                *statusCode = 0x0207;
                                /*
                                 * iSCSI Initiator error
                                 */
                        }
                        break;

                case iscsiAuthDebugStatusAuthMethodNotPresent:
                case iscsiAuthDebugStatusAuthMethodReject:
                case iscsiAuthDebugStatusAuthMethodNone:
                case iscsiAuthDebugStatusChapAlgorithmReject:
                case iscsiAuthDebugStatusChapChallengeReflected:
                case iscsiAuthDebugStatusPasswordIdentical:
                        /*
                         * Could not authenticate with peer.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0300;
                                /*
                                 * iSCSI Target error
                                 */
                        } else {
                                *statusCode = 0x0201;
                                /*
                                 * iSCSI Initiator error
                                 */
                        }
                        break;

                case iscsiAuthDebugStatusLocalPasswordNotSet:
                        /*
                         * Local password not set.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0200;
                                /*
                                 * iSCSI Initiator error
                                 */
                        } else {
                                *statusCode = 0x0201;
                                /*
                                 * iSCSI Target error
                                 */
                        }
                        break;

                case iscsiAuthDebugStatusChapIdentifierBad:
                case iscsiAuthDebugStatusChapChallengeBad:
                case iscsiAuthDebugStatusChapResponseBad:
                case iscsiAuthDebugStatusUnexpectedKeyPresent:
                case iscsiAuthDebugStatusTbitSetIllegal:
                case iscsiAuthDebugStatusTbitSetPremature:
                case iscsiAuthDebugStatusRecvMessageCountLimit:
                case iscsiAuthDebugStatusRecvDuplicateSetKeyValue:
                case iscsiAuthDebugStatusRecvStringTooLong:
                case iscsiAuthDebugStatusRecvTooMuchData:
                        /*
                         * Other error with peer.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0300;
                                /*
                                 * iSCSI Target error
                                 */
                        } else {
                                *statusCode = 0x0200;
                                /*
                                 * iSCSI Initiator error
                                 */
                        }
                        break;

                case iscsiAuthDebugStatusNotSet:
                case iscsiAuthDebugStatusAuthPass:
                case iscsiAuthDebugStatusAuthRemoteFalse:
                case iscsiAuthDebugStatusAuthMethodBad:
                case iscsiAuthDebugStatusChapAlgorithmBad:
                case iscsiAuthDebugStatusPasswordDecryptFailed:
                case iscsiAuthDebugStatusPasswordTooShortWithNoIpSec:
                case iscsiAuthDebugStatusAuthServerError:
                case iscsiAuthDebugStatusAuthStatusBad:
                case iscsiAuthDebugStatusAuthPassNotValid:
                case iscsiAuthDebugStatusSendDuplicateSetKeyValue:
                case iscsiAuthDebugStatusSendStringTooLong:
                case iscsiAuthDebugStatusSendTooMuchData:
                default:
                        /*
                         * Error on this side.
                         */
                        if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                                *statusCode = 0x0200;
                                /*
                                 * iSCSI Initiator error
                                 */
                        } else {
                                *statusCode = 0x0300;
                                /*
                                 * iSCSI Target error
                                 */
                        }

                }
                break;

        case iscsiAuthStatusNoError:
        case iscsiAuthStatusError:
        case iscsiAuthStatusContinue:
        case iscsiAuthStatusInProgress:
        default:
                /*
                 * Bad authStatus
                 */
                if (client->nodeType == iscsiAuthNodeTypeInitiator) {
                        *statusCode = 0x0200;
                        /*
                         * iSCSI Initiator error
                         */
                } else {
                        *statusCode = 0x0300;
                        /*
                         * iSCSI Target error
                         */
                }
        }

        return (iscsiAuthStatusNoError);
}


int
iscsiAuthClientGetDebugStatus(IscsiAuthClient * client, int *value)
{
        if (!client || client->signature != iscsiAuthClientSignature) {
                return (iscsiAuthStatusError);
        }

        if (client->phase != iscsiAuthPhaseDone) {

                client->phase = iscsiAuthPhaseError;
                return (iscsiAuthStatusError);
        }

        *value = client->debugStatus;

        return (iscsiAuthStatusNoError);
}


const char *
iscsiAuthClientDebugStatusToText(int debugStatus)
{
        const char *s;

        switch (debugStatus) {
        case iscsiAuthDebugStatusNotSet:
                s = "Debug status not set";
                break;

        case iscsiAuthDebugStatusAuthPass:
                s = "Authentication request passed";
                break;

        case iscsiAuthDebugStatusAuthRemoteFalse:
                s = "Authentication not enabled";
                break;

        case iscsiAuthDebugStatusAuthFail:
                s = "Authentication request failed";
                break;

        case iscsiAuthDebugStatusAuthMethodBad:
                s = "AuthMethod bad";
                break;

        case iscsiAuthDebugStatusChapAlgorithmBad:
                s = "CHAP algorithm bad";
                break;

        case iscsiAuthDebugStatusPasswordDecryptFailed:
                s = "Decrypt password failed";
                break;

        case iscsiAuthDebugStatusPasswordTooShortWithNoIpSec:
                s = "Local password too short with no IPSec";
                break;

        case iscsiAuthDebugStatusAuthServerError:
                s = "Unexpected error from authentication server";
                break;

        case iscsiAuthDebugStatusAuthStatusBad:
                s = "Authentication request status bad";
                break;

        case iscsiAuthDebugStatusAuthPassNotValid:
                s = "Authentication pass status not valid";
                break;

        case iscsiAuthDebugStatusSendDuplicateSetKeyValue:
                s = "Same key set more than once on send";
                break;

        case iscsiAuthDebugStatusSendStringTooLong:
                s = "Key value too long on send";
                break;

        case iscsiAuthDebugStatusSendTooMuchData:
                s = "Too much data on send";
                break;

        case iscsiAuthDebugStatusAuthMethodExpected:
                s = "AuthMethod key expected";
                break;

        case iscsiAuthDebugStatusChapAlgorithmExpected:
                s = "CHAP algorithm key expected";
                break;

        case iscsiAuthDebugStatusChapIdentifierExpected:
                s = "CHAP identifier expected";
                break;

        case iscsiAuthDebugStatusChapChallengeExpected:
                s = "CHAP challenge expected";
                break;

        case iscsiAuthDebugStatusChapResponseExpected:
                s = "CHAP response expected";
                break;

        case iscsiAuthDebugStatusChapUsernameExpected:
                s = "CHAP username expected";
                break;

        case iscsiAuthDebugStatusAuthMethodNotPresent:
                s = "AuthMethod key not present";
                break;

        case iscsiAuthDebugStatusAuthMethodReject:
                s = "AuthMethod negotiation failed";
                break;

        case iscsiAuthDebugStatusAuthMethodNone:
                s = "AuthMethod negotiated to none";
                break;

        case iscsiAuthDebugStatusChapAlgorithmReject:
                s = "CHAP algorithm negotiation failed";
                break;

        case iscsiAuthDebugStatusChapChallengeReflected:
                s = "CHAP challange reflected";
                break;

        case iscsiAuthDebugStatusPasswordIdentical:
                s = "Local password same as remote";
                break;

        case iscsiAuthDebugStatusLocalPasswordNotSet:
                s = "Local password not set";
                break;

        case iscsiAuthDebugStatusChapIdentifierBad:
                s = "CHAP identifier bad";
                break;

        case iscsiAuthDebugStatusChapChallengeBad:
                s = "CHAP challenge bad";
                break;

        case iscsiAuthDebugStatusChapResponseBad:
                s = "CHAP response bad";
                break;

        case iscsiAuthDebugStatusUnexpectedKeyPresent:
                s = "Unexpected key present";
                break;

        case iscsiAuthDebugStatusTbitSetIllegal:
                s = "T bit set on response, but not on previous message";
                break;

        case iscsiAuthDebugStatusTbitSetPremature:
                s = "T bit set on response, but authenticaton not complete";
                break;

        case iscsiAuthDebugStatusRecvMessageCountLimit:
                s = "Message count limit reached on receive";
                break;

        case iscsiAuthDebugStatusRecvDuplicateSetKeyValue:
                s = "Same key set more than once on receive";
                break;

        case iscsiAuthDebugStatusRecvStringTooLong:
                s = "Key value too long on receive";
                break;

        case iscsiAuthDebugStatusRecvTooMuchData:
                s = "Too much data on receive";
                break;

        default:
                s = "Unknown error";
        }

        return (s);
}