/*
 * iSCSI connection daemon
 * Copyright (C) 2001 Cisco Systems, Inc.
 * maintained by linux-iscsi@cisco.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * See the file COPYING included with this distribution for more details.
 *
 * $Id: iscsiAuthClient.c,v 1.13 2003/02/14 21:54:52 smferris Exp $
 */

/*
 * This file implements the iSCSI CHAP authentication method based on
 * draft-ietf-ips-iscsi-15.txt.  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 "iscsiAuthClient.h"


#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif


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 */
	} 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 */
	memset(outData, 0, iscsiAuthStringMaxLength);

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

	iscsiAuthMd5Final(responseData, &context);

	return iscsiAuthDebugStatusNotSet; /* no error */
}


static void
iscsiAuthClientInitKeyBlock(IscsiAuthKeyBlock *keyBlock)

{
	char *stringBlock = keyBlock->stringBlock;

	memset(keyBlock, 0, 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) {
			iscsiAuthClientStringCopy(
				client->scratchKeyValue, s, iscsiAuthStringMaxLength);
		} else {
			iscsiAuthClientStringAppend(
				client->scratchKeyValue, ",", iscsiAuthStringMaxLength);
			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) {
			iscsiAuthClientStringCopy(
				client->scratchKeyValue, s, iscsiAuthStringMaxLength);
		} else {
			iscsiAuthClientStringAppend(
				client->scratchKeyValue, ",", iscsiAuthStringMaxLength);
			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;
		}

		/* Fall through */

	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;
		}

		/* Fall through */

	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 &&
			memcmp(
				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;
		}

		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;
		}
		/* Fall through */

	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);

			if (debugStatus == iscsiAuthDebugStatusNotSet &&
				memcmp(
					myResponseData,
					responseData,
					iscsiAuthChapResponseLength) == 0) {

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

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

		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;

		/* Fall through */

	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;

		/* Fall through */

	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) {

			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) {

					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) {

					/*
					 * 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) {
		return iscsiAuthStatusError;
	}

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

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

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

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

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

	memset(client, 0, sizeof(*client));
	memset(recvStringBlock, 0, sizeof(*recvStringBlock));
	memset(sendStringBlock, 0, sizeof(*sendStringBlock));
	memset(recvChapChallenge, 0, sizeof(*recvChapChallenge));
	memset(sendChapChallenge, 0, 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;
	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;
	}

	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);

	memset(client, 0, 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;
	}

	memcpy(client->passwordData, passwordData, passwordLength);
	client->passwordLength = passwordLength;
	client->passwordPresent = TRUE;

	/* 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 || 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;
}
