mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
According to https://www.iana.org/assignments/ppp-numbers/ppp-numbers.xml#ppp-numbers-9 the value of the SHA-1 algorithm is 6 and not 7, as it is now in iscsi-scst/usr/chap.c. If on a client side node.session.auth.chap_algs is SHA1, login fails. Signed-off-by: Lev Vainblat <lev@zadarastorage.com> git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@9171 d57e44dd-8a1f-0410-8b47-8ef2f437770f
667 lines
17 KiB
C
667 lines
17 KiB
C
/*
|
|
* chap.c - support for (mutual) CHAP authentication.
|
|
*
|
|
* Copyright (C) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
|
|
* Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>,
|
|
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
|
|
* Copyright (C) 2007 - 2018 Western Digital Corporation
|
|
*
|
|
* and code taken from UNH iSCSI software:
|
|
* Copyright (C) 2001-2003 InterOperability Lab (IOL)
|
|
* University of New Hampshire (UNH)
|
|
* Durham, NH 03824
|
|
*
|
|
* 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, version 2
|
|
* of the License.
|
|
*
|
|
* 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.
|
|
*
|
|
* Heavily based on code from UNH iSCSI iscsid.c
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include "sha1.h"
|
|
#include "md5.h"
|
|
|
|
#include "iscsid.h"
|
|
|
|
#define HEX_FORMAT 0x01
|
|
#define BASE64_FORMAT 0x02
|
|
|
|
#define CHAP_DIGEST_ALG_MD5 5
|
|
#define CHAP_DIGEST_ALG_SHA1 6
|
|
|
|
#define CHAP_MD5_DIGEST_LEN 16
|
|
#define CHAP_SHA1_DIGEST_LEN 20
|
|
|
|
#define CHAP_INITIATOR_ERROR -1
|
|
#define CHAP_AUTH_ERROR -2
|
|
#define CHAP_TARGET_ERROR -3
|
|
|
|
#define CHAP_AUTH_STATE_START AUTH_STATE_START
|
|
#define CHAP_AUTH_STATE_CHALLENGE 1
|
|
#define CHAP_AUTH_STATE_RESPONSE 2
|
|
|
|
#define CHAP_INITIATOR_AUTH 0
|
|
#define CHAP_TARGET_AUTH 1
|
|
|
|
#define CHAP_CHALLENGE_MAX 50
|
|
|
|
static inline int decode_hex_digit(char c)
|
|
{
|
|
switch (c) {
|
|
case '0' ... '9':
|
|
return c - '0';
|
|
case 'a' ... 'f':
|
|
return c - 'a' + 10;
|
|
case 'A' ... 'F':
|
|
return c - 'A' + 10;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void decode_hex_string(char *hex_string, u8 *intnum, int intlen)
|
|
{
|
|
char *ptr;
|
|
int j;
|
|
|
|
j = strlen(hex_string);
|
|
ptr = hex_string + j;
|
|
j = --intlen;
|
|
do {
|
|
intnum[j] = decode_hex_digit(*--ptr);
|
|
intnum[j] |= decode_hex_digit(*--ptr) << 4;
|
|
j--;
|
|
} while (ptr > hex_string);
|
|
|
|
while (j >= 0)
|
|
intnum[j--] = 0;
|
|
}
|
|
|
|
/* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
|
|
static u8 decode_base64_digit(char base64)
|
|
{
|
|
switch (base64) {
|
|
case '=':
|
|
return 64;
|
|
case '/':
|
|
return 63;
|
|
case '+':
|
|
return 62;
|
|
default:
|
|
if ((base64 >= 'A') && (base64 <= 'Z'))
|
|
return base64 - 'A';
|
|
else if ((base64 >= 'a') && (base64 <= 'z'))
|
|
return 26 + (base64 - 'a');
|
|
else if ((base64 >= '0') && (base64 <= '9'))
|
|
return 52 + (base64 - '0');
|
|
else
|
|
//XXX This return value should be unsigned; and anyway
|
|
//XXX in case of a bad character in the string, our
|
|
//XXX caller (sometimes) checks for 65, not 255 or -1
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
|
|
static void decode_base64_string(char *string, u8 *intnum, int int_len)
|
|
{
|
|
int len;
|
|
int count;
|
|
int intptr;
|
|
u8 num[4];
|
|
int octets;
|
|
|
|
if ((string == NULL) || (intnum == NULL))
|
|
return;
|
|
len = strlen(string);
|
|
if (len == 0)
|
|
return;
|
|
if ((len % 4) != 0)
|
|
return;
|
|
count = 0;
|
|
intptr = 0;
|
|
while (count < len - 4) {
|
|
num[0] = decode_base64_digit(string[count]);
|
|
num[1] = decode_base64_digit(string[count + 1]);
|
|
num[2] = decode_base64_digit(string[count + 2]);
|
|
num[3] = decode_base64_digit(string[count + 3]);
|
|
if ((num[0] == 65) || (num[1] == 65) || (num[2] == 65) || (num[3] == 65))
|
|
return;
|
|
count += 4;
|
|
octets =
|
|
(num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
|
|
intnum[intptr] = (octets & 0xFF0000) >> 16;
|
|
intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
|
|
intnum[intptr + 2] = octets & 0x0000FF;
|
|
intptr += 3;
|
|
}
|
|
num[0] = decode_base64_digit(string[count]);
|
|
num[1] = decode_base64_digit(string[count + 1]);
|
|
num[2] = decode_base64_digit(string[count + 2]);
|
|
num[3] = decode_base64_digit(string[count + 3]);
|
|
//XXX Check for the special "bad character in string" value here like above?
|
|
//XXX Also check the string for missing/incorrect padding?
|
|
if ((num[0] == 64) || (num[1] == 64))
|
|
return;
|
|
if (num[2] == 64) {
|
|
if (num[3] != 64)
|
|
return;
|
|
intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
|
|
} else if (num[3] == 64) {
|
|
intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
|
|
intnum[intptr + 1] = (num[1] << 4) | (num[2] >> 2);
|
|
} else {
|
|
octets =
|
|
(num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
|
|
intnum[intptr] = (octets & 0xFF0000) >> 16;
|
|
intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
|
|
intnum[intptr + 2] = octets & 0x0000FF;
|
|
}
|
|
}
|
|
|
|
static inline void encode_hex_string(u8 *intnum, long length, char *string)
|
|
{
|
|
int i;
|
|
char *strptr;
|
|
|
|
strptr = string;
|
|
for (i = 0; i < length; i++, strptr += 2)
|
|
sprintf(strptr, "%.2hhx", intnum[i]);
|
|
}
|
|
|
|
/* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
|
|
static void encode_base64_string(u8 *intnum, long length, char *string)
|
|
{
|
|
int count, octets, strptr, delta;
|
|
static const char base64code[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
|
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
|
'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
|
|
'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
|
'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
|
'q', 'r', 's', 't', 'u', 'v', 'w',
|
|
'x', 'y', 'z', '0', '1', '2', '3',
|
|
'4', '5', '6', '7', '8', '9', '+',
|
|
'/', '=' };
|
|
|
|
if ((!intnum) || (!string) || (!length))
|
|
return;
|
|
|
|
count = 0;
|
|
octets = 0;
|
|
strptr = 0;
|
|
|
|
while ((delta = (length - count)) > 2) {
|
|
octets = (intnum[count] << 16) | (intnum[count + 1] << 8) | intnum[count + 2];
|
|
string[strptr] = base64code[(octets & 0xfc0000) >> 18];
|
|
string[strptr + 1] = base64code[(octets & 0x03f000) >> 12];
|
|
string[strptr + 2] = base64code[(octets & 0x000fc0) >> 6];
|
|
string[strptr + 3] = base64code[octets & 0x00003f];
|
|
count += 3;
|
|
strptr += 4;
|
|
}
|
|
if (delta == 1) {
|
|
string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
|
|
string[strptr + 1] = base64code[(intnum[count] & 0x03) << 4];
|
|
string[strptr + 2] = base64code[64];
|
|
string[strptr + 3] = base64code[64];
|
|
strptr += 4;
|
|
} else if (delta == 2) {
|
|
string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
|
|
string[strptr + 1] = base64code[((intnum[count] & 0x03) << 4) | ((intnum[count + 1] & 0xf0) >> 4)];
|
|
string[strptr + 2] = base64code[(intnum[count + 1] & 0x0f) << 2];
|
|
string[strptr + 3] = base64code[64];
|
|
strptr += 4;
|
|
}
|
|
string[strptr] = '\0';
|
|
}
|
|
|
|
static inline int chap_check_encoding_format(char *encoded)
|
|
{
|
|
int encoding_fmt;
|
|
|
|
if (!encoded)
|
|
return -1;
|
|
if ((strlen(encoded) < 3) || (encoded[0] != '0'))
|
|
return -1;
|
|
|
|
if (encoded[1] == 'x' || encoded[1] == 'X')
|
|
encoding_fmt = HEX_FORMAT;
|
|
else if (encoded[1] == 'b' || encoded[1] == 'B')
|
|
encoding_fmt = BASE64_FORMAT;
|
|
else
|
|
return -1;
|
|
|
|
return encoding_fmt;
|
|
}
|
|
|
|
static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt)
|
|
{
|
|
int i;
|
|
int decode_len = 0;
|
|
|
|
i = strlen(encoded);
|
|
i -= 2;
|
|
|
|
if (encoding_fmt == HEX_FORMAT)
|
|
decode_len = (i - 1) / 2 + 1;
|
|
else if (encoding_fmt == BASE64_FORMAT) {
|
|
if (i % 4)
|
|
return CHAP_INITIATOR_ERROR;
|
|
|
|
decode_len = i / 4 * 3;
|
|
if (encoded[i + 1] == '=')
|
|
decode_len--;
|
|
if (encoded[i] == '=')
|
|
decode_len--;
|
|
}
|
|
|
|
if (!decode_len)
|
|
return CHAP_INITIATOR_ERROR;
|
|
|
|
*decode_buf = malloc(decode_len);
|
|
if (!*decode_buf)
|
|
return CHAP_TARGET_ERROR;
|
|
|
|
return decode_len;
|
|
}
|
|
|
|
static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt)
|
|
{
|
|
if (encoding_fmt == HEX_FORMAT) {
|
|
if ((strlen(encoded) - 2) > (2 * buf_len)) {
|
|
log_error("%s(%d) BUG? "
|
|
" buf[%d] !sufficient to decode string[%d]",
|
|
__func__, __LINE__, buf_len, (int) strlen(encoded));
|
|
return CHAP_TARGET_ERROR;
|
|
}
|
|
decode_hex_string(encoded + 2, decode_buf, buf_len);
|
|
|
|
} else if (encoding_fmt == BASE64_FORMAT) {
|
|
if ((strlen(encoded) - 2) > ((buf_len - 1) / 3 + 1) * 4) {
|
|
log_error("%s(%d) BUG? "
|
|
" buf[%d] !sufficient to decode string[%d]",
|
|
__func__, __LINE__, buf_len, (int) strlen(encoded));
|
|
return CHAP_TARGET_ERROR;
|
|
}
|
|
decode_base64_string(encoded + 2, decode_buf, buf_len);
|
|
|
|
} else
|
|
return CHAP_INITIATOR_ERROR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt)
|
|
{
|
|
encode_buf[0] = '0';
|
|
if (encoding_fmt == HEX_FORMAT) {
|
|
encode_buf[1] = 'x';
|
|
encode_hex_string(intnum, buf_len, encode_buf + 2);
|
|
} else if (encoding_fmt == BASE64_FORMAT) {
|
|
encode_buf[1] = 'b';
|
|
encode_base64_string(intnum, buf_len, encode_buf + 2);
|
|
}
|
|
}
|
|
|
|
static inline void chap_calc_digest_md5(char chap_id, const char *secret, int secret_len,
|
|
const u8 *challenge, int challenge_len, u8 *digest)
|
|
{
|
|
struct md5_ctx ctx;
|
|
|
|
md5_init(&ctx);
|
|
md5_update(&ctx, &chap_id, 1);
|
|
md5_update(&ctx, secret, secret_len);
|
|
md5_update(&ctx, challenge, challenge_len);
|
|
md5_final(&ctx, digest);
|
|
}
|
|
|
|
static inline void chap_calc_digest_sha1(char chap_id, const char *secret, int secret_len,
|
|
const u8 *challenge, int challenge_len, u8 *digest)
|
|
{
|
|
struct sha1_ctx ctx;
|
|
|
|
sha1_init(&ctx);
|
|
sha1_update(&ctx, &chap_id, 1);
|
|
sha1_update(&ctx, secret, secret_len);
|
|
sha1_update(&ctx, challenge, challenge_len);
|
|
sha1_final(&ctx, digest);
|
|
}
|
|
|
|
/*
|
|
* To generate challenge for CHAP, use stronger random number generator as
|
|
* opposed to simple rand().
|
|
*/
|
|
static int chap_rand(void)
|
|
{
|
|
int fd;
|
|
int r;
|
|
|
|
fd = open("/dev/urandom", O_RDONLY);
|
|
assert(fd != -1);
|
|
if (read(fd, &r, sizeof(r)) < sizeof(r)) {
|
|
}
|
|
close(fd);
|
|
return r;
|
|
}
|
|
|
|
static int chap_initiator_auth_create_challenge(struct connection *conn)
|
|
{
|
|
char *value, *p;
|
|
char text[CHAP_CHALLENGE_MAX * 2 + 8];
|
|
static int chap_id;
|
|
int i;
|
|
|
|
value = text_key_find(conn, "CHAP_A");
|
|
if (!value)
|
|
return CHAP_INITIATOR_ERROR;
|
|
while ((p = strsep(&value, ","))) {
|
|
if (!strcmp(p, "5")) {
|
|
conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5;
|
|
conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
|
|
break;
|
|
} else if (!strcmp(p, "6")) {
|
|
conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
|
|
conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
|
|
break;
|
|
}
|
|
}
|
|
if (!p)
|
|
return CHAP_INITIATOR_ERROR;
|
|
|
|
text_key_add(conn, "CHAP_A", p);
|
|
conn->auth.chap.id = ++chap_id;
|
|
sprintf(text, "%u", (unsigned char)conn->auth.chap.id);
|
|
text_key_add(conn, "CHAP_I", text);
|
|
|
|
/*
|
|
* ToDo: does a random challenge length provide any benefits security-
|
|
* wise, or should we rather always use the max. allowed length of
|
|
* 1024 for the (unencoded) challenge?
|
|
*/
|
|
conn->auth.chap.challenge_size = (chap_rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
|
|
|
|
conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
|
|
if (!conn->auth.chap.challenge)
|
|
return CHAP_TARGET_ERROR;
|
|
|
|
p = text;
|
|
strcpy(p, "0x");
|
|
p += 2;
|
|
for (i = 0; i < conn->auth.chap.challenge_size; i++) {
|
|
conn->auth.chap.challenge[i] = chap_rand();
|
|
sprintf(p, "%.2hhx", conn->auth.chap.challenge[i]);
|
|
p += 2;
|
|
}
|
|
text_key_add(conn, "CHAP_C", text);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int chap_initiator_auth_check_response(struct connection *conn)
|
|
{
|
|
char *value;
|
|
u8 *his_digest = NULL, *our_digest = NULL;
|
|
int digest_len = 0, retval = 0, encoding_format;
|
|
char pass[ISCSI_NAME_LEN];
|
|
|
|
if (accounts_empty(conn->tid, ISCSI_USER_DIR_INCOMING)) {
|
|
log_warning("CHAP initiator auth.: "
|
|
"No CHAP credentials configured");
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (!(value = text_key_find(conn, "CHAP_N"))) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
conn->user = strdup(value);
|
|
if (conn->user == NULL) {
|
|
log_error("Unable to duplicate initiator's USER %s", value);
|
|
}
|
|
|
|
memset(pass, 0, sizeof(pass));
|
|
if (config_account_query(conn->tid, ISCSI_USER_DIR_INCOMING, value, pass) < 0) {
|
|
log_warning("CHAP initiator auth.: "
|
|
"No valid user/pass combination for initiator %s "
|
|
"found", conn->initiator);
|
|
retval = CHAP_AUTH_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (!(value = text_key_find(conn, "CHAP_R"))) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if ((encoding_format = chap_check_encoding_format(value)) < 0) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
switch (conn->auth.chap.digest_alg) {
|
|
case CHAP_DIGEST_ALG_MD5:
|
|
digest_len = CHAP_MD5_DIGEST_LEN;
|
|
break;
|
|
case CHAP_DIGEST_ALG_SHA1:
|
|
digest_len = CHAP_SHA1_DIGEST_LEN;
|
|
break;
|
|
default:
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (!(his_digest = malloc(digest_len))) {
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
if (!(our_digest = malloc(digest_len))) {
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
switch (conn->auth.chap.digest_alg) {
|
|
case CHAP_DIGEST_ALG_MD5:
|
|
chap_calc_digest_md5(conn->auth.chap.id, pass, strlen(pass),
|
|
conn->auth.chap.challenge,
|
|
conn->auth.chap.challenge_size,
|
|
our_digest);
|
|
break;
|
|
case CHAP_DIGEST_ALG_SHA1:
|
|
chap_calc_digest_sha1(conn->auth.chap.id, pass, strlen(pass),
|
|
conn->auth.chap.challenge,
|
|
conn->auth.chap.challenge_size,
|
|
our_digest);
|
|
break;
|
|
default:
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(our_digest, his_digest, digest_len)) {
|
|
log_warning("CHAP initiator auth.: "
|
|
"authentication of %s failed (wrong secret!?)",
|
|
conn->initiator);
|
|
retval = CHAP_AUTH_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
conn->state = CHAP_AUTH_STATE_RESPONSE;
|
|
out:
|
|
if (his_digest)
|
|
free(his_digest);
|
|
if (our_digest)
|
|
free(our_digest);
|
|
return retval;
|
|
}
|
|
|
|
static int chap_target_auth_create_response(struct connection *conn)
|
|
{
|
|
char chap_id, *value, *response = NULL;
|
|
u8 *challenge = NULL, *digest = NULL;
|
|
int encoding_format, response_len;
|
|
int challenge_len = 0, digest_len = 0, retval = 0;
|
|
struct iscsi_attr *user;
|
|
|
|
if (!(value = text_key_find(conn, "CHAP_I"))) {
|
|
/* Initiator doesn't want target auth!? */
|
|
conn->state = STATE_SECURITY_DONE;
|
|
retval = 0;
|
|
goto out;
|
|
}
|
|
chap_id = strtol(value, &value, 10);
|
|
|
|
user = account_get_first(conn->tid, ISCSI_USER_DIR_OUTGOING);
|
|
if (user == NULL) {
|
|
log_warning("CHAP target auth.: "
|
|
"no outgoing credentials configured%s",
|
|
conn->tid ? "." : " for discovery.");
|
|
retval = CHAP_AUTH_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (!(value = text_key_find(conn, "CHAP_C"))) {
|
|
log_warning("CHAP target auth.: "
|
|
"got no challenge from initiator %s",
|
|
conn->initiator);
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if ((encoding_format = chap_check_encoding_format(value)) < 0) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
|
|
if (retval <= 0)
|
|
goto out;
|
|
else if (retval > 1024) {
|
|
log_warning("CHAP target auth.: "
|
|
"initiator %s sent challenge of invalid length %d",
|
|
conn->initiator, challenge_len);
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
challenge_len = retval;
|
|
retval = 0;
|
|
|
|
switch (conn->auth.chap.digest_alg) {
|
|
case CHAP_DIGEST_ALG_MD5:
|
|
digest_len = CHAP_MD5_DIGEST_LEN;
|
|
break;
|
|
case CHAP_DIGEST_ALG_SHA1:
|
|
digest_len = CHAP_SHA1_DIGEST_LEN;
|
|
break;
|
|
default:
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (encoding_format == HEX_FORMAT)
|
|
response_len = 2 * digest_len;
|
|
else
|
|
response_len = ((digest_len - 1) / 3 + 1) * 4;
|
|
/* "0x" / "0b" and "\0": */
|
|
response_len += 3;
|
|
|
|
if (!(digest = malloc(digest_len))) {
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
if (!(response = malloc(response_len))) {
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
/* RFC 3720, 8.2.1: CHAP challenges MUST NOT be reused */
|
|
if (challenge_len == conn->auth.chap.challenge_size) {
|
|
if (!memcmp(challenge, conn->auth.chap.challenge,
|
|
challenge_len)) {
|
|
/* ToDo: RFC 3720 demands to close TCP conn */
|
|
log_warning("CHAP target auth.: "
|
|
"initiator %s reflected our challenge",
|
|
conn->initiator);
|
|
retval = CHAP_INITIATOR_ERROR;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
switch (conn->auth.chap.digest_alg) {
|
|
case CHAP_DIGEST_ALG_MD5:
|
|
chap_calc_digest_md5(chap_id, ISCSI_USER_PASS(user),
|
|
strlen(ISCSI_USER_PASS(user)), challenge, challenge_len, digest);
|
|
break;
|
|
case CHAP_DIGEST_ALG_SHA1:
|
|
chap_calc_digest_sha1(chap_id, ISCSI_USER_PASS(user),
|
|
strlen(ISCSI_USER_PASS(user)), challenge, challenge_len, digest);
|
|
break;
|
|
default:
|
|
retval = CHAP_TARGET_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
memset(response, 0x0, response_len);
|
|
chap_encode_string(digest, digest_len, response, encoding_format);
|
|
text_key_add(conn, "CHAP_N", ISCSI_USER_NAME(user));
|
|
text_key_add(conn, "CHAP_R", response);
|
|
|
|
conn->state = STATE_SECURITY_DONE;
|
|
out:
|
|
if (challenge)
|
|
free(challenge);
|
|
if (digest)
|
|
free(digest);
|
|
if (response)
|
|
free(response);
|
|
return retval;
|
|
}
|
|
|
|
int cmnd_exec_auth_chap(struct connection *conn)
|
|
{
|
|
int res;
|
|
|
|
switch (conn->auth_state) {
|
|
case CHAP_AUTH_STATE_START:
|
|
res = chap_initiator_auth_create_challenge(conn);
|
|
break;
|
|
case CHAP_AUTH_STATE_CHALLENGE:
|
|
res = chap_initiator_auth_check_response(conn);
|
|
if (res < 0)
|
|
break;
|
|
/* fall through */
|
|
case CHAP_AUTH_STATE_RESPONSE:
|
|
res = chap_target_auth_create_response(conn);
|
|
break;
|
|
default:
|
|
log_error("%s(%d): BUG. unknown conn->auth_state %d",
|
|
__func__, __LINE__, conn->auth_state);
|
|
res = CHAP_TARGET_ERROR;
|
|
}
|
|
|
|
return res;
|
|
}
|