Index: head/usr.sbin/ctld/Makefile =================================================================== --- head/usr.sbin/ctld/Makefile (revision 273458) +++ head/usr.sbin/ctld/Makefile (revision 273459) @@ -1,21 +1,21 @@ # $FreeBSD$ PROG= ctld -SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h +SRCS= chap.c ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h CFLAGS+= -I${.CURDIR} CFLAGS+= -I${.CURDIR}/../../sys CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi #CFLAGS+= -DICL_KERNEL_PROXY MAN= ctld.8 ctl.conf.5 DPADD= ${LIBBSDXML} ${LIBCAM} ${LIBCRYPTO} ${LIBL} ${LIBSBUF} ${LIBSSL} ${LIBUTIL} LDADD= -lbsdxml -lcam -lcrypto -ll -lsbuf -lssl -lutil YFLAGS+= -v CLEANFILES= y.tab.c y.tab.h y.output WARNS= 6 NO_WMISSING_VARIABLE_DECLARATIONS= .include Index: head/usr.sbin/ctld/chap.c =================================================================== --- head/usr.sbin/ctld/chap.c (nonexistent) +++ head/usr.sbin/ctld/chap.c (revision 273459) @@ -0,0 +1,386 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include "ctld.h" + +static void +chap_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, sizeof(id)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +static int +chap_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = chap_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + free(bin); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +struct chap * +chap_new(void) +{ + struct chap *chap; + int rv; + + chap = calloc(sizeof(*chap), 1); + if (chap == NULL) + log_err(1, "calloc"); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(chap->chap_challenge, sizeof(chap->chap_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&chap->chap_id, sizeof(chap->chap_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + return (chap); +} + +char * +chap_get_id(const struct chap *chap) +{ + char *chap_i; + int ret; + + ret = asprintf(&chap_i, "%d", chap->chap_id); + if (ret < 0) + log_err(1, "asprintf"); + + return (chap_i); +} + +char * +chap_get_challenge(const struct chap *chap) +{ + char *chap_c; + + chap_c = chap_bin2hex(chap->chap_challenge, + sizeof(chap->chap_challenge)); + + return (chap_c); +} + +static int +chap_receive_bin(struct chap *chap, void *response, size_t response_len) +{ + + if (response_len != sizeof(chap->chap_response)) { + log_debugx("got CHAP response with invalid length; " + "got %zd, should be %zd", + response_len, sizeof(chap->chap_response)); + return (1); + } + + memcpy(chap->chap_response, response, response_len); + return (0); +} + +int +chap_receive(struct chap *chap, const char *response) +{ + void *response_bin; + size_t response_bin_len; + int error; + + error = chap_hex2bin(response, &response_bin, &response_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP response \"%s\"", + response); + return (1); + } + + error = chap_receive_bin(chap, response_bin, response_bin_len); + free(response_bin); + + return (error); +} + +int +chap_authenticate(struct chap *chap, const char *secret) +{ + char expected_response[MD5_DIGEST_LENGTH]; + + chap_compute_md5(chap->chap_id, secret, + chap->chap_challenge, sizeof(chap->chap_challenge), + expected_response, sizeof(expected_response)); + + if (memcmp(chap->chap_response, + expected_response, sizeof(expected_response)) != 0) { + return (-1); + } + + return (0); +} + +void +chap_delete(struct chap *chap) +{ + + free(chap); +} + +struct rchap * +rchap_new(const char *secret) +{ + struct rchap *rchap; + + rchap = calloc(sizeof(*rchap), 1); + if (rchap == NULL) + log_err(1, "calloc"); + + rchap->rchap_secret = checked_strdup(secret); + + return (rchap); +} + +static void +rchap_receive_bin(struct rchap *rchap, const unsigned char id, + const void *challenge, size_t challenge_len) +{ + + rchap->rchap_id = id; + rchap->rchap_challenge = calloc(challenge_len, 1); + if (rchap->rchap_challenge == NULL) + log_err(1, "calloc"); + memcpy(rchap->rchap_challenge, challenge, challenge_len); + rchap->rchap_challenge_len = challenge_len; +} + +int +rchap_receive(struct rchap *rchap, const char *id, const char *challenge) +{ + unsigned char id_bin; + void *challenge_bin; + size_t challenge_bin_len; + + int error; + + id_bin = strtoul(id, NULL, 10); + + error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP challenge \"%s\"", + challenge); + return (1); + } + + rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); + free(challenge_bin); + + return (0); +} + +static void +rchap_get_response_bin(struct rchap *rchap, + void **responsep, size_t *response_lenp) +{ + void *response_bin; + size_t response_bin_len = MD5_DIGEST_LENGTH; + + response_bin = calloc(response_bin_len, 1); + if (response_bin == NULL) + log_err(1, "calloc"); + + chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, + rchap->rchap_challenge, rchap->rchap_challenge_len, + response_bin, response_bin_len); + + *responsep = response_bin; + *response_lenp = response_bin_len; +} + +char * +rchap_get_response(struct rchap *rchap) +{ + void *response; + size_t response_len; + char *chap_r; + + rchap_get_response_bin(rchap, &response, &response_len); + chap_r = chap_bin2hex(response, response_len); + free(response); + + return (chap_r); +} + +void +rchap_delete(struct rchap *rchap) +{ + + free(rchap->rchap_secret); + free(rchap->rchap_challenge); + free(rchap); +} Property changes on: head/usr.sbin/ctld/chap.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/usr.sbin/ctld/ctld.h =================================================================== --- head/usr.sbin/ctld/ctld.h (revision 273458) +++ head/usr.sbin/ctld/ctld.h (revision 273459) @@ -1,329 +1,359 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef CTLD_H #define CTLD_H #include #ifdef ICL_KERNEL_PROXY #include #endif #include #include #include +#include #define DEFAULT_CONFIG_PATH "/etc/ctl.conf" #define DEFAULT_PIDFILE "/var/run/ctld.pid" #define DEFAULT_BLOCKSIZE 512 #define MAX_NAME_LEN 223 #define MAX_DATA_SEGMENT_LENGTH (128 * 1024) #define MAX_BURST_LENGTH 16776192 struct auth { TAILQ_ENTRY(auth) a_next; struct auth_group *a_auth_group; char *a_user; char *a_secret; char *a_mutual_user; char *a_mutual_secret; }; struct auth_name { TAILQ_ENTRY(auth_name) an_next; struct auth_group *an_auth_group; char *an_initator_name; }; struct auth_portal { TAILQ_ENTRY(auth_portal) ap_next; struct auth_group *ap_auth_group; char *ap_initator_portal; struct sockaddr_storage ap_sa; int ap_mask; }; #define AG_TYPE_UNKNOWN 0 #define AG_TYPE_DENY 1 #define AG_TYPE_NO_AUTHENTICATION 2 #define AG_TYPE_CHAP 3 #define AG_TYPE_CHAP_MUTUAL 4 struct auth_group { TAILQ_ENTRY(auth_group) ag_next; struct conf *ag_conf; char *ag_name; struct target *ag_target; int ag_type; TAILQ_HEAD(, auth) ag_auths; TAILQ_HEAD(, auth_name) ag_names; TAILQ_HEAD(, auth_portal) ag_portals; }; struct portal { TAILQ_ENTRY(portal) p_next; struct portal_group *p_portal_group; bool p_iser; char *p_listen; struct addrinfo *p_ai; #ifdef ICL_KERNEL_PROXY int p_id; #endif TAILQ_HEAD(, target) p_targets; int p_socket; }; struct portal_group { TAILQ_ENTRY(portal_group) pg_next; struct conf *pg_conf; char *pg_name; struct auth_group *pg_discovery_auth_group; bool pg_unassigned; TAILQ_HEAD(, portal) pg_portals; uint16_t pg_tag; }; struct lun_option { TAILQ_ENTRY(lun_option) lo_next; struct lun *lo_lun; char *lo_name; char *lo_value; }; struct lun { TAILQ_ENTRY(lun) l_next; TAILQ_HEAD(, lun_option) l_options; struct target *l_target; int l_lun; char *l_backend; int l_blocksize; char *l_device_id; char *l_path; char *l_serial; int64_t l_size; int l_ctl_lun; }; struct target { TAILQ_ENTRY(target) t_next; TAILQ_HEAD(, lun) t_luns; struct conf *t_conf; struct auth_group *t_auth_group; struct portal_group *t_portal_group; char *t_name; char *t_alias; }; struct conf { char *conf_pidfile_path; TAILQ_HEAD(, target) conf_targets; TAILQ_HEAD(, auth_group) conf_auth_groups; TAILQ_HEAD(, portal_group) conf_portal_groups; int conf_debug; int conf_timeout; int conf_maxproc; uint16_t conf_last_portal_group_tag; #ifdef ICL_KERNEL_PROXY int conf_portal_id; #endif struct pidfh *conf_pidfh; bool conf_default_pg_defined; bool conf_default_ag_defined; bool conf_kernel_port_on; }; #define CONN_SESSION_TYPE_NONE 0 #define CONN_SESSION_TYPE_DISCOVERY 1 #define CONN_SESSION_TYPE_NORMAL 2 #define CONN_DIGEST_NONE 0 #define CONN_DIGEST_CRC32C 1 struct connection { struct portal *conn_portal; struct target *conn_target; int conn_socket; int conn_session_type; char *conn_initiator_name; char *conn_initiator_addr; char *conn_initiator_alias; uint8_t conn_initiator_isid[6]; struct sockaddr_storage conn_initiator_sa; uint32_t conn_cmdsn; uint32_t conn_statsn; size_t conn_max_data_segment_length; size_t conn_max_burst_length; int conn_immediate_data; int conn_header_digest; int conn_data_digest; }; struct pdu { struct connection *pdu_connection; struct iscsi_bhs *pdu_bhs; char *pdu_data; size_t pdu_data_len; }; #define KEYS_MAX 1024 struct keys { char *keys_names[KEYS_MAX]; char *keys_values[KEYS_MAX]; char *keys_data; size_t keys_data_len; }; + +#define CHAP_CHALLENGE_LEN 1024 + +struct chap { + unsigned char chap_id; + char chap_challenge[CHAP_CHALLENGE_LEN]; + char chap_response[MD5_DIGEST_LENGTH]; +}; + +struct rchap { + char *rchap_secret; + unsigned char rchap_id; + void *rchap_challenge; + size_t rchap_challenge_len; +}; + +struct chap *chap_new(void); +char *chap_get_id(const struct chap *chap); +char *chap_get_challenge(const struct chap *chap); +int chap_receive(struct chap *chap, const char *response); +int chap_authenticate(struct chap *chap, + const char *secret); +void chap_delete(struct chap *chap); + +struct rchap *rchap_new(const char *secret); +int rchap_receive(struct rchap *rchap, + const char *id, const char *challenge); +char *rchap_get_response(struct rchap *rchap); +void rchap_delete(struct rchap *rchap); struct conf *conf_new(void); struct conf *conf_new_from_file(const char *path); struct conf *conf_new_from_kernel(void); void conf_delete(struct conf *conf); int conf_verify(struct conf *conf); struct auth_group *auth_group_new(struct conf *conf, const char *name); void auth_group_delete(struct auth_group *ag); struct auth_group *auth_group_find(const struct conf *conf, const char *name); int auth_group_set_type_str(struct auth_group *ag, const char *type); const struct auth *auth_new_chap(struct auth_group *ag, const char *user, const char *secret); const struct auth *auth_new_chap_mutual(struct auth_group *ag, const char *user, const char *secret, const char *user2, const char *secret2); const struct auth *auth_find(const struct auth_group *ag, const char *user); const struct auth_name *auth_name_new(struct auth_group *ag, const char *initiator_name); bool auth_name_defined(const struct auth_group *ag); const struct auth_name *auth_name_find(const struct auth_group *ag, const char *initiator_name); const struct auth_portal *auth_portal_new(struct auth_group *ag, const char *initiator_portal); bool auth_portal_defined(const struct auth_group *ag); const struct auth_portal *auth_portal_find(const struct auth_group *ag, const struct sockaddr_storage *sa); struct portal_group *portal_group_new(struct conf *conf, const char *name); void portal_group_delete(struct portal_group *pg); struct portal_group *portal_group_find(const struct conf *conf, const char *name); int portal_group_add_listen(struct portal_group *pg, const char *listen, bool iser); struct target *target_new(struct conf *conf, const char *name); void target_delete(struct target *target); struct target *target_find(struct conf *conf, const char *name); struct lun *lun_new(struct target *target, int lun_id); void lun_delete(struct lun *lun); struct lun *lun_find(const struct target *target, int lun_id); void lun_set_backend(struct lun *lun, const char *value); void lun_set_blocksize(struct lun *lun, size_t value); void lun_set_device_id(struct lun *lun, const char *value); void lun_set_path(struct lun *lun, const char *value); void lun_set_serial(struct lun *lun, const char *value); void lun_set_size(struct lun *lun, size_t value); void lun_set_ctl_lun(struct lun *lun, uint32_t value); struct lun_option *lun_option_new(struct lun *lun, const char *name, const char *value); void lun_option_delete(struct lun_option *clo); struct lun_option *lun_option_find(const struct lun *lun, const char *name); void lun_option_set(struct lun_option *clo, const char *value); void kernel_init(void); int kernel_lun_add(struct lun *lun); int kernel_lun_resize(struct lun *lun); int kernel_lun_remove(struct lun *lun); void kernel_handoff(struct connection *conn); int kernel_port_add(struct target *targ); int kernel_port_remove(struct target *targ); void kernel_capsicate(void); #ifdef ICL_KERNEL_PROXY void kernel_listen(struct addrinfo *ai, bool iser, int portal_id); void kernel_accept(int *connection_id, int *portal_id, struct sockaddr *client_sa, socklen_t *client_salen); void kernel_send(struct pdu *pdu); void kernel_receive(struct pdu *pdu); #endif struct keys *keys_new(void); void keys_delete(struct keys *keys); void keys_load(struct keys *keys, const struct pdu *pdu); void keys_save(struct keys *keys, struct pdu *pdu); const char *keys_find(struct keys *keys, const char *name); int keys_find_int(struct keys *keys, const char *name); void keys_add(struct keys *keys, const char *name, const char *value); void keys_add_int(struct keys *keys, const char *name, int value); struct pdu *pdu_new(struct connection *conn); struct pdu *pdu_new_response(struct pdu *request); void pdu_delete(struct pdu *pdu); void pdu_receive(struct pdu *request); void pdu_send(struct pdu *response); void login(struct connection *conn); void discovery(struct connection *conn); void log_init(int level); void log_set_peer_name(const char *name); void log_set_peer_addr(const char *addr); void log_err(int, const char *, ...) __dead2 __printflike(2, 3); void log_errx(int, const char *, ...) __dead2 __printflike(2, 3); void log_warn(const char *, ...) __printflike(1, 2); void log_warnx(const char *, ...) __printflike(1, 2); void log_debugx(const char *, ...) __printflike(1, 2); char *checked_strdup(const char *); bool valid_iscsi_name(const char *name); bool timed_out(void); #endif /* !CTLD_H */ Index: head/usr.sbin/ctld/login.c =================================================================== --- head/usr.sbin/ctld/login.c (revision 273458) +++ head/usr.sbin/ctld/login.c (revision 273459) @@ -1,1092 +1,920 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include -#include -#include -#include #include "ctld.h" #include "iscsi_proto.h" static void login_send_error(struct pdu *request, char class, char detail); static void login_set_nsg(struct pdu *response, int nsg) { struct iscsi_bhs_login_response *bhslr; assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr->bhslr_flags &= 0xFC; bhslr->bhslr_flags |= nsg; } static int login_csg(const struct pdu *request) { struct iscsi_bhs_login_request *bhslr; bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; return ((bhslr->bhslr_flags & 0x0C) >> 2); } static void login_set_csg(struct pdu *response, int csg) { struct iscsi_bhs_login_response *bhslr; assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || csg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr->bhslr_flags &= 0xF3; bhslr->bhslr_flags |= csg << 2; } static struct pdu * login_receive(struct connection *conn, bool initial) { struct pdu *request; struct iscsi_bhs_login_request *bhslr; request = pdu_new(conn); pdu_receive(request); if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != ISCSI_BHS_OPCODE_LOGIN_REQUEST) { /* * The first PDU in session is special - if we receive any PDU * different than login request, we have to drop the connection * without sending response ("A target receiving any PDU * except a Login request before the Login Phase is started MUST * immediately terminate the connection on which the PDU * was received.") */ if (initial == false) login_send_error(request, 0x02, 0x0b); log_errx(1, "protocol error: received invalid opcode 0x%x", request->pdu_bhs->bhs_opcode); } bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; /* * XXX: Implement the C flag some day. */ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { login_send_error(request, 0x03, 0x00); log_errx(1, "received Login PDU with unsupported \"C\" flag"); } if (bhslr->bhslr_version_max != 0x00) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with unsupported " "Version-max 0x%x", bhslr->bhslr_version_max); } if (bhslr->bhslr_version_min != 0x00) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with unsupported " "Version-min 0x%x", bhslr->bhslr_version_min); } if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with decreasing CmdSN: " "was %d, is %d", conn->conn_cmdsn, ntohl(bhslr->bhslr_cmdsn)); } if (initial == false && ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with wrong ExpStatSN: " "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), conn->conn_statsn); } conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); return (request); } static struct pdu * login_new_response(struct pdu *request) { struct pdu *response; struct connection *conn; struct iscsi_bhs_login_request *bhslr; struct iscsi_bhs_login_response *bhslr2; bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; conn = request->pdu_connection; response = pdu_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE; login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION); memcpy(bhslr2->bhslr_isid, bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid)); bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); return (response); } static void login_send_error(struct pdu *request, char class, char detail) { struct pdu *response; struct iscsi_bhs_login_response *bhslr2; log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; " "see next line for reason", class, detail); response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_status_class = class; bhslr2->bhslr_status_detail = detail; pdu_send(response); pdu_delete(response); } static int login_list_contains(const char *list, const char *what) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, what) == 0) { free(tofree); return (1); } } free(tofree); return (0); } static int login_list_prefers(const char *list, const char *choice1, const char *choice2) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, choice1) == 0) { free(tofree); return (1); } if (strcmp(token, choice2) == 0) { free(tofree); return (2); } } free(tofree); return (-1); } -static int -login_hex2int(const char hex) -{ - switch (hex) { - case '0': - return (0x00); - case '1': - return (0x01); - case '2': - return (0x02); - case '3': - return (0x03); - case '4': - return (0x04); - case '5': - return (0x05); - case '6': - return (0x06); - case '7': - return (0x07); - case '8': - return (0x08); - case '9': - return (0x09); - case 'a': - case 'A': - return (0x0a); - case 'b': - case 'B': - return (0x0b); - case 'c': - case 'C': - return (0x0c); - case 'd': - case 'D': - return (0x0d); - case 'e': - case 'E': - return (0x0e); - case 'f': - case 'F': - return (0x0f); - default: - return (-1); - } -} - -/* - * XXX: Review this _carefully_. - */ -static int -login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) -{ - int i, hex_len, nibble; - bool lo = true; /* As opposed to 'hi'. */ - char *bin; - size_t bin_off, bin_len; - - if (strncasecmp(hex, "0x", strlen("0x")) != 0) { - log_warnx("malformed variable, should start with \"0x\""); - return (-1); - } - - hex += strlen("0x"); - hex_len = strlen(hex); - if (hex_len < 1) { - log_warnx("malformed variable; doesn't contain anything " - "but \"0x\""); - return (-1); - } - - bin_len = hex_len / 2 + hex_len % 2; - bin = calloc(bin_len, 1); - if (bin == NULL) - log_err(1, "calloc"); - - bin_off = bin_len - 1; - for (i = hex_len - 1; i >= 0; i--) { - nibble = login_hex2int(hex[i]); - if (nibble < 0) { - log_warnx("malformed variable, invalid char \"%c\"", - hex[i]); - return (-1); - } - - assert(bin_off < bin_len); - if (lo) { - bin[bin_off] = nibble; - lo = false; - } else { - bin[bin_off] |= nibble << 4; - bin_off--; - lo = true; - } - } - - *binp = bin; - *bin_lenp = bin_len; - return (0); -} - -static char * -login_bin2hex(const char *bin, size_t bin_len) -{ - unsigned char *hex, *tmp, ch; - size_t hex_len; - size_t i; - - hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ - hex = malloc(hex_len); - if (hex == NULL) - log_err(1, "malloc"); - - tmp = hex; - tmp += sprintf(tmp, "0x"); - for (i = 0; i < bin_len; i++) { - ch = bin[i]; - tmp += sprintf(tmp, "%02x", ch); - } - - return (hex); -} - -static void -login_compute_md5(const char id, const char *secret, - const void *challenge, size_t challenge_len, void *response, - size_t response_len) -{ - MD5_CTX ctx; - int rv; - - assert(response_len == MD5_DIGEST_LENGTH); - - MD5_Init(&ctx); - MD5_Update(&ctx, &id, sizeof(id)); - MD5_Update(&ctx, secret, strlen(secret)); - MD5_Update(&ctx, challenge, challenge_len); - rv = MD5_Final(response, &ctx); - if (rv != 1) - log_errx(1, "MD5_Final"); -} - -#define LOGIN_CHALLENGE_LEN 1024 - static struct pdu * login_receive_chap_a(struct connection *conn) { struct pdu *request; struct keys *request_keys; const char *chap_a; request = login_receive(conn, false); request_keys = keys_new(); keys_load(request_keys, request); chap_a = keys_find(request_keys, "CHAP_A"); if (chap_a == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_A"); } if (login_list_contains(chap_a, "5") == 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login PDU with unsupported CHAP_A " "\"%s\"", chap_a); } keys_delete(request_keys); return (request); } static void -login_send_chap_c(struct pdu *request, const unsigned char id, - const void *challenge, const size_t challenge_len) +login_send_chap_c(struct pdu *request, struct chap *chap) { struct pdu *response; struct keys *response_keys; - char *chap_c, chap_i[4]; + char *chap_c, *chap_i; - chap_c = login_bin2hex(challenge, challenge_len); - snprintf(chap_i, sizeof(chap_i), "%d", id); + chap_c = chap_get_challenge(chap); + chap_i = chap_get_id(chap); response = login_new_response(request); response_keys = keys_new(); keys_add(response_keys, "CHAP_A", "5"); keys_add(response_keys, "CHAP_I", chap_i); keys_add(response_keys, "CHAP_C", chap_c); + free(chap_i); free(chap_c); keys_save(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); } static struct pdu * -login_receive_chap_r(struct connection *conn, - struct auth_group *ag, const unsigned char id, const void *challenge, - const size_t challenge_len, const struct auth **cap) +login_receive_chap_r(struct connection *conn, struct auth_group *ag, + struct chap *chap, const struct auth **authp) { struct pdu *request; struct keys *request_keys; const char *chap_n, *chap_r; - char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; - size_t response_bin_len; const struct auth *auth; int error; request = login_receive(conn, false); request_keys = keys_new(); keys_load(request_keys, request); chap_n = keys_find(request_keys, "CHAP_N"); if (chap_n == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_N"); } chap_r = keys_find(request_keys, "CHAP_R"); if (chap_r == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_R"); } - error = login_hex2bin(chap_r, &response_bin, &response_bin_len); + error = chap_receive(chap, chap_r); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); } /* * Verify the response. */ assert(ag->ag_type == AG_TYPE_CHAP || ag->ag_type == AG_TYPE_CHAP_MUTUAL); auth = auth_find(ag, chap_n); if (auth == NULL) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login with invalid user \"%s\"", chap_n); } assert(auth->a_secret != NULL); assert(strlen(auth->a_secret) > 0); - login_compute_md5(id, auth->a_secret, challenge, - challenge_len, expected_response_bin, - sizeof(expected_response_bin)); - if (memcmp(response_bin, expected_response_bin, - sizeof(expected_response_bin)) != 0) { + error = chap_authenticate(chap, auth->a_secret); + if (error != 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "CHAP authentication failed for user \"%s\"", auth->a_user); } keys_delete(request_keys); - free(response_bin); - *cap = auth; + *authp = auth; return (request); } static void login_send_chap_success(struct pdu *request, const struct auth *auth) { struct pdu *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr2; + struct rchap *rchap; const char *chap_i, *chap_c; - char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; - size_t challenge_len; - unsigned char id; + char *chap_r; int error; response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); /* * Actually, one more thing: mutual authentication. */ request_keys = keys_new(); keys_load(request_keys, request); chap_i = keys_find(request_keys, "CHAP_I"); chap_c = keys_find(request_keys, "CHAP_C"); if (chap_i != NULL || chap_c != NULL) { if (chap_i == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "initiator requested target " "authentication, but didn't send CHAP_I"); } if (chap_c == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "initiator requested target " "authentication, but didn't send CHAP_C"); } if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator requests target authentication " "for user \"%s\", but mutual user/secret " "is not set", auth->a_user); } - id = strtoul(chap_i, NULL, 10); - error = login_hex2bin(chap_c, &challenge, &challenge_len); + log_debugx("performing mutual authentication as user \"%s\"", + auth->a_mutual_user); + + rchap = rchap_new(auth->a_mutual_secret); + error = rchap_receive(rchap, chap_i, chap_c); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed " - "CHAP_C"); + "CHAP_I or CHAP_C"); } - - log_debugx("performing mutual authentication as user \"%s\"", - auth->a_mutual_user); - login_compute_md5(id, auth->a_mutual_secret, challenge, - challenge_len, response_bin, sizeof(response_bin)); - - chap_r = login_bin2hex(response_bin, - sizeof(response_bin)); + chap_r = rchap_get_response(rchap); + rchap_delete(rchap); response_keys = keys_new(); keys_add(response_keys, "CHAP_N", auth->a_mutual_user); keys_add(response_keys, "CHAP_R", chap_r); free(chap_r); keys_save(response_keys, response); keys_delete(response_keys); } else { log_debugx("initiator did not request target authentication"); } keys_delete(request_keys); pdu_send(response); pdu_delete(response); } static void login_chap(struct connection *conn, struct auth_group *ag) { const struct auth *auth; + struct chap *chap; struct pdu *request; - char challenge_bin[LOGIN_CHALLENGE_LEN]; - unsigned char id; - int rv; /* * Receive CHAP_A PDU. */ log_debugx("beginning CHAP authentication; waiting for CHAP_A"); request = login_receive_chap_a(conn); /* * Generate the challenge. */ - rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); - if (rv != 1) { - login_send_error(request, 0x03, 0x02); - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - rv = RAND_bytes(&id, sizeof(id)); - if (rv != 1) { - login_send_error(request, 0x03, 0x02); - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } + chap = chap_new(); /* * Send the challenge. */ log_debugx("sending CHAP_C, binary challenge size is %zd bytes", - sizeof(challenge_bin)); - login_send_chap_c(request, id, challenge_bin, - sizeof(challenge_bin)); + sizeof(chap->chap_challenge)); + login_send_chap_c(request, chap); pdu_delete(request); /* * Receive CHAP_N/CHAP_R PDU and authenticate. */ log_debugx("waiting for CHAP_N/CHAP_R"); - request = login_receive_chap_r(conn, ag, id, challenge_bin, - sizeof(challenge_bin), &auth); + request = login_receive_chap_r(conn, ag, chap, &auth); /* * Yay, authentication succeeded! */ log_debugx("authentication succeeded for user \"%s\"; " "transitioning to Negotiation Phase", auth->a_user); login_send_chap_success(request, auth); pdu_delete(request); + chap_delete(chap); } static void login_negotiate_key(struct pdu *request, const char *name, const char *value, bool skipped_security, struct keys *response_keys) { int which, tmp; struct connection *conn; conn = request->pdu_connection; if (strcmp(name, "InitiatorName") == 0) { if (!skipped_security) log_errx(1, "initiator resent InitiatorName"); } else if (strcmp(name, "SessionType") == 0) { if (!skipped_security) log_errx(1, "initiator resent SessionType"); } else if (strcmp(name, "TargetName") == 0) { if (!skipped_security) log_errx(1, "initiator resent TargetName"); } else if (strcmp(name, "InitiatorAlias") == 0) { if (conn->conn_initiator_alias != NULL) free(conn->conn_initiator_alias); conn->conn_initiator_alias = checked_strdup(value); } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ } else if (strcmp(name, "HeaderDigest") == 0) { /* * We don't handle digests for discovery sessions. */ if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; } which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("initiator prefers CRC32C " "for header digest; we'll use it"); conn->conn_header_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: log_debugx("initiator prefers not to do " "header digest; we'll comply"); keys_add(response_keys, name, "None"); break; default: log_warnx("initiator sent unrecognized " "HeaderDigest value \"%s\"; will use None", value); keys_add(response_keys, name, "None"); break; } } else if (strcmp(name, "DataDigest") == 0) { if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; } which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("initiator prefers CRC32C " "for data digest; we'll use it"); conn->conn_data_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: log_debugx("initiator prefers not to do " "data digest; we'll comply"); keys_add(response_keys, name, "None"); break; default: log_warnx("initiator sent unrecognized " "DataDigest value \"%s\"; will use None", value); keys_add(response_keys, name, "None"); break; } } else if (strcmp(name, "MaxConnections") == 0) { keys_add(response_keys, name, "1"); } else if (strcmp(name, "InitialR2T") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "ImmediateData") == 0) { if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; ImmediateData irrelevant"); keys_add(response_keys, name, "Irrelevant"); } else { if (strcmp(value, "Yes") == 0) { conn->conn_immediate_data = true; keys_add(response_keys, name, "Yes"); } else { conn->conn_immediate_data = false; keys_add(response_keys, name, "No"); } } } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid " "MaxRecvDataSegmentLength"); } if (tmp > MAX_DATA_SEGMENT_LENGTH) { log_debugx("capping MaxRecvDataSegmentLength " "from %d to %d", tmp, MAX_DATA_SEGMENT_LENGTH); tmp = MAX_DATA_SEGMENT_LENGTH; } conn->conn_max_data_segment_length = tmp; keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "MaxBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid MaxBurstLength"); } if (tmp > MAX_BURST_LENGTH) { log_debugx("capping MaxBurstLength from %d to %d", tmp, MAX_BURST_LENGTH); tmp = MAX_BURST_LENGTH; } conn->conn_max_burst_length = tmp; keys_add(response_keys, name, value); } else if (strcmp(name, "FirstBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid " "FirstBurstLength"); } if (tmp > MAX_DATA_SEGMENT_LENGTH) { log_debugx("capping FirstBurstLength from %d to %d", tmp, MAX_DATA_SEGMENT_LENGTH); tmp = MAX_DATA_SEGMENT_LENGTH; } /* * We don't pass the value to the kernel; it only enforces * hardcoded limit anyway. */ keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "DefaultTime2Wait") == 0) { keys_add(response_keys, name, value); } else if (strcmp(name, "DefaultTime2Retain") == 0) { keys_add(response_keys, name, "0"); } else if (strcmp(name, "MaxOutstandingR2T") == 0) { keys_add(response_keys, name, "1"); } else if (strcmp(name, "DataPDUInOrder") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "DataSequenceInOrder") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { keys_add(response_keys, name, "0"); } else if (strcmp(name, "OFMarker") == 0) { keys_add(response_keys, name, "No"); } else if (strcmp(name, "IFMarker") == 0) { keys_add(response_keys, name, "No"); } else { log_debugx("unknown key \"%s\"; responding " "with NotUnderstood", name); keys_add(response_keys, name, "NotUnderstood"); } } static void login_negotiate(struct connection *conn, struct pdu *request) { struct pdu *response; struct iscsi_bhs_login_response *bhslr2; struct keys *request_keys, *response_keys; int i; bool skipped_security; if (request == NULL) { log_debugx("beginning operational parameter negotiation; " "waiting for Login PDU"); request = login_receive(conn, false); skipped_security = false; } else skipped_security = true; request_keys = keys_new(); keys_load(request_keys, request); response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; bhslr2->bhslr_tsih = htons(0xbadd); login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE); response_keys = keys_new(); if (skipped_security && conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn->conn_target->t_alias != NULL) keys_add(response_keys, "TargetAlias", conn->conn_target->t_alias); keys_add_int(response_keys, "TargetPortalGroupTag", conn->conn_portal->p_portal_group->pg_tag); } for (i = 0; i < KEYS_MAX; i++) { if (request_keys->keys_names[i] == NULL) break; login_negotiate_key(request, request_keys->keys_names[i], request_keys->keys_values[i], skipped_security, response_keys); } log_debugx("operational parameter negotiation done; " "transitioning to Full Feature Phase"); keys_save(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); pdu_delete(request); keys_delete(request_keys); } void login(struct connection *conn) { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; struct iscsi_bhs_login_response *bhslr2; struct keys *request_keys, *response_keys; struct auth_group *ag; const char *initiator_name, *initiator_alias, *session_type, *target_name, *auth_method; /* * Handle the initial Login Request - figure out required authentication * method and either transition to the next phase, if no authentication * is required, or call appropriate authentication code. */ log_debugx("beginning Login Phase; waiting for Login PDU"); request = login_receive(conn, true); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; if (bhslr->bhslr_tsih != 0) { login_send_error(request, 0x02, 0x0a); log_errx(1, "received Login PDU with non-zero TSIH"); } memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid, sizeof(conn->conn_initiator_isid)); /* * XXX: Implement the C flag some day. */ request_keys = keys_new(); keys_load(request_keys, request); assert(conn->conn_initiator_name == NULL); initiator_name = keys_find(request_keys, "InitiatorName"); if (initiator_name == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without InitiatorName"); } if (valid_iscsi_name(initiator_name) == false) { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid InitiatorName"); } conn->conn_initiator_name = checked_strdup(initiator_name); log_set_peer_name(conn->conn_initiator_name); /* * XXX: This doesn't work (does nothing) because of Capsicum. */ setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name); initiator_alias = keys_find(request_keys, "InitiatorAlias"); if (initiator_alias != NULL) conn->conn_initiator_alias = checked_strdup(initiator_alias); assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE); session_type = keys_find(request_keys, "SessionType"); if (session_type != NULL) { if (strcmp(session_type, "Normal") == 0) { conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; } else if (strcmp(session_type, "Discovery") == 0) { conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY; } else { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid " "SessionType \"%s\"", session_type); } } else conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; assert(conn->conn_target == NULL); if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { target_name = keys_find(request_keys, "TargetName"); if (target_name == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without TargetName"); } conn->conn_target = target_find(conn->conn_portal->p_portal_group->pg_conf, target_name); if (conn->conn_target == NULL) { login_send_error(request, 0x02, 0x03); log_errx(1, "requested target \"%s\" not found", target_name); } } /* * At this point we know what kind of authentication we need. */ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { ag = conn->conn_target->t_auth_group; if (ag->ag_name != NULL) { log_debugx("initiator requests to connect " "to target \"%s\"; auth-group \"%s\"", conn->conn_target->t_name, conn->conn_target->t_auth_group->ag_name); } else { log_debugx("initiator requests to connect " "to target \"%s\"", conn->conn_target->t_name); } } else { assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); ag = conn->conn_portal->p_portal_group->pg_discovery_auth_group; if (ag->ag_name != NULL) { log_debugx("initiator requests " "discovery session; auth-group \"%s\"", ag->ag_name); } else { log_debugx("initiator requests discovery session"); } } /* * Enforce initiator-name and initiator-portal. */ if (auth_name_defined(ag)) { if (auth_name_find(ag, initiator_name) == NULL) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed " "initiator names"); } log_debugx("initiator matches allowed initiator names"); } else { log_debugx("auth-group does not define initiator name " "restrictions"); } if (auth_portal_defined(ag)) { if (auth_portal_find(ag, &conn->conn_initiator_sa) == NULL) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed " "initiator portals"); } log_debugx("initiator matches allowed initiator portals"); } else { log_debugx("auth-group does not define initiator portal " "restrictions"); } /* * Let's see if the initiator intends to do any kind of authentication * at all. */ if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator skipped the authentication, " "but authentication is required"); } keys_delete(request_keys); log_debugx("initiator skipped the authentication, " "and we don't need it; proceeding with negotiation"); login_negotiate(conn, request); return; } if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) { /* * Initiator might want to to authenticate, * but we don't need it. */ log_debugx("authentication not required; " "transitioning to operational parameter negotiation"); if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) log_warnx("initiator did not set the \"T\" flag; " "transitioning anyway"); response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); response_keys = keys_new(); /* * Required by Linux initiator. */ auth_method = keys_find(request_keys, "AuthMethod"); if (auth_method != NULL && login_list_contains(auth_method, "None")) keys_add(response_keys, "AuthMethod", "None"); if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn->conn_target->t_alias != NULL) keys_add(response_keys, "TargetAlias", conn->conn_target->t_alias); keys_add_int(response_keys, "TargetPortalGroupTag", conn->conn_portal->p_portal_group->pg_tag); } keys_save(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); pdu_delete(request); keys_delete(request_keys); login_negotiate(conn, NULL); return; } if (ag->ag_type == AG_TYPE_DENY) { login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type is \"deny\""); } if (ag->ag_type == AG_TYPE_UNKNOWN) { /* * This can happen with empty auth-group. */ login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type not set, denying access"); } log_debugx("CHAP authentication required"); auth_method = keys_find(request_keys, "AuthMethod"); if (auth_method == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without AuthMethod"); } /* * XXX: This should be Reject, not just a login failure (5.3.2). */ if (login_list_contains(auth_method, "CHAP") == 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator requests unsupported AuthMethod \"%s\" " "instead of \"CHAP\"", auth_method); } response = login_new_response(request); response_keys = keys_new(); keys_add(response_keys, "AuthMethod", "CHAP"); if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn->conn_target->t_alias != NULL) keys_add(response_keys, "TargetAlias", conn->conn_target->t_alias); keys_add_int(response_keys, "TargetPortalGroupTag", conn->conn_portal->p_portal_group->pg_tag); } keys_save(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); pdu_delete(request); keys_delete(request_keys); login_chap(conn, ag); login_negotiate(conn, NULL); } Index: head/usr.sbin/iscsid/Makefile =================================================================== --- head/usr.sbin/iscsid/Makefile (revision 273458) +++ head/usr.sbin/iscsid/Makefile (revision 273459) @@ -1,16 +1,16 @@ # $FreeBSD$ PROG= iscsid -SRCS= discovery.c iscsid.c keys.c log.c login.c pdu.c +SRCS= chap.c discovery.c iscsid.c keys.c log.c login.c pdu.c CFLAGS+= -I${.CURDIR} CFLAGS+= -I${.CURDIR}/../../sys/cam CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi #CFLAGS+= -DICL_KERNEL_PROXY MAN= iscsid.8 DPADD= ${LIBCRYPTO} ${LIBSSL} ${LIBUTIL} LDADD= -lcrypto -lssl -lutil WARNS= 6 .include Index: head/usr.sbin/iscsid/chap.c =================================================================== --- head/usr.sbin/iscsid/chap.c (nonexistent) +++ head/usr.sbin/iscsid/chap.c (revision 273459) @@ -0,0 +1,386 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include "iscsid.h" + +static void +chap_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, sizeof(id)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +static int +chap_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = chap_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + free(bin); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +struct chap * +chap_new(void) +{ + struct chap *chap; + int rv; + + chap = calloc(sizeof(*chap), 1); + if (chap == NULL) + log_err(1, "calloc"); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(chap->chap_challenge, sizeof(chap->chap_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&chap->chap_id, sizeof(chap->chap_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + return (chap); +} + +char * +chap_get_id(const struct chap *chap) +{ + char *chap_i; + int ret; + + ret = asprintf(&chap_i, "%d", chap->chap_id); + if (ret < 0) + log_err(1, "asprintf"); + + return (chap_i); +} + +char * +chap_get_challenge(const struct chap *chap) +{ + char *chap_c; + + chap_c = chap_bin2hex(chap->chap_challenge, + sizeof(chap->chap_challenge)); + + return (chap_c); +} + +static int +chap_receive_bin(struct chap *chap, void *response, size_t response_len) +{ + + if (response_len != sizeof(chap->chap_response)) { + log_debugx("got CHAP response with invalid length; " + "got %zd, should be %zd", + response_len, sizeof(chap->chap_response)); + return (1); + } + + memcpy(chap->chap_response, response, response_len); + return (0); +} + +int +chap_receive(struct chap *chap, const char *response) +{ + void *response_bin; + size_t response_bin_len; + int error; + + error = chap_hex2bin(response, &response_bin, &response_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP response \"%s\"", + response); + return (1); + } + + error = chap_receive_bin(chap, response_bin, response_bin_len); + free(response_bin); + + return (error); +} + +int +chap_authenticate(struct chap *chap, const char *secret) +{ + char expected_response[MD5_DIGEST_LENGTH]; + + chap_compute_md5(chap->chap_id, secret, + chap->chap_challenge, sizeof(chap->chap_challenge), + expected_response, sizeof(expected_response)); + + if (memcmp(chap->chap_response, + expected_response, sizeof(expected_response)) != 0) { + return (-1); + } + + return (0); +} + +void +chap_delete(struct chap *chap) +{ + + free(chap); +} + +struct rchap * +rchap_new(const char *secret) +{ + struct rchap *rchap; + + rchap = calloc(sizeof(*rchap), 1); + if (rchap == NULL) + log_err(1, "calloc"); + + rchap->rchap_secret = checked_strdup(secret); + + return (rchap); +} + +static void +rchap_receive_bin(struct rchap *rchap, const unsigned char id, + const void *challenge, size_t challenge_len) +{ + + rchap->rchap_id = id; + rchap->rchap_challenge = calloc(challenge_len, 1); + if (rchap->rchap_challenge == NULL) + log_err(1, "calloc"); + memcpy(rchap->rchap_challenge, challenge, challenge_len); + rchap->rchap_challenge_len = challenge_len; +} + +int +rchap_receive(struct rchap *rchap, const char *id, const char *challenge) +{ + unsigned char id_bin; + void *challenge_bin; + size_t challenge_bin_len; + + int error; + + id_bin = strtoul(id, NULL, 10); + + error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP challenge \"%s\"", + challenge); + return (1); + } + + rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); + free(challenge_bin); + + return (0); +} + +static void +rchap_get_response_bin(struct rchap *rchap, + void **responsep, size_t *response_lenp) +{ + void *response_bin; + size_t response_bin_len = MD5_DIGEST_LENGTH; + + response_bin = calloc(response_bin_len, 1); + if (response_bin == NULL) + log_err(1, "calloc"); + + chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, + rchap->rchap_challenge, rchap->rchap_challenge_len, + response_bin, response_bin_len); + + *responsep = response_bin; + *response_lenp = response_bin_len; +} + +char * +rchap_get_response(struct rchap *rchap) +{ + void *response; + size_t response_len; + char *chap_r; + + rchap_get_response_bin(rchap, &response, &response_len); + chap_r = chap_bin2hex(response, response_len); + free(response); + + return (chap_r); +} + +void +rchap_delete(struct rchap *rchap) +{ + + free(rchap->rchap_secret); + free(rchap->rchap_challenge); + free(rchap); +} Property changes on: head/usr.sbin/iscsid/chap.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/usr.sbin/iscsid/iscsid.h =================================================================== --- head/usr.sbin/iscsid/iscsid.h (revision 273458) +++ head/usr.sbin/iscsid/iscsid.h (revision 273459) @@ -1,119 +1,148 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef ISCSID_H #define ISCSID_H #include #include +#include #include #define DEFAULT_PIDFILE "/var/run/iscsid.pid" #define CONN_DIGEST_NONE 0 #define CONN_DIGEST_CRC32C 1 #define CONN_MUTUAL_CHALLENGE_LEN 1024 struct connection { int conn_iscsi_fd; int conn_socket; unsigned int conn_session_id; struct iscsi_session_conf conn_conf; char conn_target_alias[ISCSI_ADDR_LEN]; uint8_t conn_isid[6]; uint16_t conn_tsih; uint32_t conn_statsn; int conn_header_digest; int conn_data_digest; bool conn_initial_r2t; bool conn_immediate_data; size_t conn_max_data_segment_length; size_t conn_max_burst_length; size_t conn_first_burst_length; - char conn_mutual_challenge[CONN_MUTUAL_CHALLENGE_LEN]; - unsigned char conn_mutual_id; + struct chap *conn_mutual_chap; }; struct pdu { struct connection *pdu_connection; struct iscsi_bhs *pdu_bhs; char *pdu_data; size_t pdu_data_len; }; #define KEYS_MAX 1024 struct keys { char *keys_names[KEYS_MAX]; char *keys_values[KEYS_MAX]; char *keys_data; size_t keys_data_len; }; + +#define CHAP_CHALLENGE_LEN 1024 + +struct chap { + unsigned char chap_id; + char chap_challenge[CHAP_CHALLENGE_LEN]; + char chap_response[MD5_DIGEST_LENGTH]; +}; + +struct rchap { + char *rchap_secret; + unsigned char rchap_id; + void *rchap_challenge; + size_t rchap_challenge_len; +}; + +struct chap *chap_new(void); +char *chap_get_id(const struct chap *chap); +char *chap_get_challenge(const struct chap *chap); +int chap_receive(struct chap *chap, const char *response); +int chap_authenticate(struct chap *chap, + const char *secret); +void chap_delete(struct chap *chap); + +struct rchap *rchap_new(const char *secret); +int rchap_receive(struct rchap *rchap, + const char *id, const char *challenge); +char *rchap_get_response(struct rchap *rchap); +void rchap_delete(struct rchap *rchap); struct keys *keys_new(void); void keys_delete(struct keys *key); void keys_load(struct keys *keys, const struct pdu *pdu); void keys_save(struct keys *keys, struct pdu *pdu); const char *keys_find(struct keys *keys, const char *name); int keys_find_int(struct keys *keys, const char *name); void keys_add(struct keys *keys, const char *name, const char *value); void keys_add_int(struct keys *keys, const char *name, int value); struct pdu *pdu_new(struct connection *ic); struct pdu *pdu_new_response(struct pdu *request); void pdu_receive(struct pdu *request); void pdu_send(struct pdu *response); void pdu_delete(struct pdu *ip); void login(struct connection *ic); void discovery(struct connection *ic); void log_init(int level); void log_set_peer_name(const char *name); void log_set_peer_addr(const char *addr); void log_err(int, const char *, ...) __dead2 __printflike(2, 3); void log_errx(int, const char *, ...) __dead2 __printflike(2, 3); void log_warn(const char *, ...) __printflike(1, 2); void log_warnx(const char *, ...) __printflike(1, 2); void log_debugx(const char *, ...) __printflike(1, 2); char *checked_strdup(const char *); bool timed_out(void); void fail(const struct connection *, const char *); #endif /* !ISCSID_H */ Index: head/usr.sbin/iscsid/login.c =================================================================== --- head/usr.sbin/iscsid/login.c (revision 273458) +++ head/usr.sbin/iscsid/login.c (revision 273459) @@ -1,981 +1,822 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include -#include -#include -#include #include "iscsid.h" #include "iscsi_proto.h" static int login_nsg(const struct pdu *response) { struct iscsi_bhs_login_response *bhslr; bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; return (bhslr->bhslr_flags & 0x03); } static void login_set_nsg(struct pdu *request, int nsg) { struct iscsi_bhs_login_request *bhslr; assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_flags &= 0xFC; bhslr->bhslr_flags |= nsg; } static void login_set_csg(struct pdu *request, int csg) { struct iscsi_bhs_login_request *bhslr; assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || csg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_flags &= 0xF3; bhslr->bhslr_flags |= csg << 2; } static const char * login_target_error_str(int class, int detail) { static char msg[128]; /* * RFC 3270, 10.13.5. Status-Class and Status-Detail */ switch (class) { case 0x01: switch (detail) { case 0x01: return ("Target moved temporarily"); case 0x02: return ("Target moved permanently"); default: snprintf(msg, sizeof(msg), "unknown redirection; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } case 0x02: switch (detail) { case 0x00: return ("Initiator error"); case 0x01: return ("Authentication failure"); case 0x02: return ("Authorization failure"); case 0x03: return ("Not found"); case 0x04: return ("Target removed"); case 0x05: return ("Unsupported version"); case 0x06: return ("Too many connections"); case 0x07: return ("Missing parameter"); case 0x08: return ("Can't include in session"); case 0x09: return ("Session type not supported"); case 0x0a: return ("Session does not exist"); case 0x0b: return ("Invalid during login"); default: snprintf(msg, sizeof(msg), "unknown initiator error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } case 0x03: switch (detail) { case 0x00: return ("Target error"); case 0x01: return ("Service unavailable"); case 0x02: return ("Out of resources"); default: snprintf(msg, sizeof(msg), "unknown target error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } default: snprintf(msg, sizeof(msg), "unknown error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } } static void kernel_modify(const struct connection *conn, const char *target_address) { struct iscsi_session_modify ism; int error; memset(&ism, 0, sizeof(ism)); ism.ism_session_id = conn->conn_session_id; memcpy(&ism.ism_conf, &conn->conn_conf, sizeof(ism.ism_conf)); strlcpy(ism.ism_conf.isc_target_addr, target_address, sizeof(ism.ism_conf.isc_target)); error = ioctl(conn->conn_iscsi_fd, ISCSISMODIFY, &ism); if (error != 0) { log_err(1, "failed to redirect to %s: ISCSISMODIFY", target_address); } } /* * XXX: The way it works is suboptimal; what should happen is described * in draft-gilligan-iscsi-fault-tolerance-00. That, however, would * be much more complicated: we would need to keep "dependencies" * for sessions, so that, in case described in draft and using draft * terminology, we would have three sessions: one for discovery, * one for initial target portal, and one for redirect portal. * This would allow us to "backtrack" on connection failure, * as described in draft. */ static void login_handle_redirection(struct connection *conn, struct pdu *response) { struct iscsi_bhs_login_response *bhslr; struct keys *response_keys; const char *target_address; bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; assert (bhslr->bhslr_status_class == 1); response_keys = keys_new(); keys_load(response_keys, response); target_address = keys_find(response_keys, "TargetAddress"); if (target_address == NULL) log_errx(1, "received redirection without TargetAddress"); if (target_address[0] == '\0') log_errx(1, "received redirection with empty TargetAddress"); if (strlen(target_address) >= sizeof(conn->conn_conf.isc_target_addr) - 1) log_errx(1, "received TargetAddress is too long"); log_debugx("received redirection to \"%s\"", target_address); kernel_modify(conn, target_address); keys_delete(response_keys); } static struct pdu * login_receive(struct connection *conn) { struct pdu *response; struct iscsi_bhs_login_response *bhslr; const char *errorstr; static bool initial = true; response = pdu_new(conn); pdu_receive(response); if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { log_errx(1, "protocol error: received invalid opcode 0x%x", response->pdu_bhs->bhs_opcode); } bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; /* * XXX: Implement the C flag some day. */ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) log_errx(1, "received Login PDU with unsupported \"C\" flag"); if (bhslr->bhslr_version_max != 0x00) log_errx(1, "received Login PDU with unsupported " "Version-max 0x%x", bhslr->bhslr_version_max); if (bhslr->bhslr_version_active != 0x00) log_errx(1, "received Login PDU with unsupported " "Version-active 0x%x", bhslr->bhslr_version_active); if (bhslr->bhslr_status_class == 1) { login_handle_redirection(conn, response); log_debugx("redirection handled; exiting"); exit(0); } if (bhslr->bhslr_status_class != 0) { errorstr = login_target_error_str(bhslr->bhslr_status_class, bhslr->bhslr_status_detail); fail(conn, errorstr); log_errx(1, "target returned error: %s", errorstr); } if (initial == false && ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { /* * It's a warning, not an error, to work around what seems * to be bug in NetBSD iSCSI target. */ log_warnx("received Login PDU with wrong StatSN: " "is %d, should be %d", ntohl(bhslr->bhslr_statsn), conn->conn_statsn + 1); } conn->conn_tsih = ntohs(bhslr->bhslr_tsih); conn->conn_statsn = ntohl(bhslr->bhslr_statsn); initial = false; return (response); } static struct pdu * login_new_request(struct connection *conn, int csg) { struct pdu *request; struct iscsi_bhs_login_request *bhslr; int nsg; request = pdu_new(conn); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | ISCSI_BHS_OPCODE_IMMEDIATE; bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; switch (csg) { case BHSLR_STAGE_SECURITY_NEGOTIATION: nsg = BHSLR_STAGE_OPERATIONAL_NEGOTIATION; break; case BHSLR_STAGE_OPERATIONAL_NEGOTIATION: nsg = BHSLR_STAGE_FULL_FEATURE_PHASE; break; default: assert(!"invalid csg"); log_errx(1, "invalid csg %d", csg); } login_set_csg(request, csg); login_set_nsg(request, nsg); memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid)); bhslr->bhslr_tsih = htons(conn->conn_tsih); bhslr->bhslr_initiator_task_tag = 0; bhslr->bhslr_cmdsn = 0; bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); return (request); } static int login_list_prefers(const char *list, const char *choice1, const char *choice2) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, choice1) == 0) { free(tofree); return (1); } if (strcmp(token, choice2) == 0) { free(tofree); return (2); } } free(tofree); return (-1); } -static int -login_hex2int(const char hex) -{ - switch (hex) { - case '0': - return (0x00); - case '1': - return (0x01); - case '2': - return (0x02); - case '3': - return (0x03); - case '4': - return (0x04); - case '5': - return (0x05); - case '6': - return (0x06); - case '7': - return (0x07); - case '8': - return (0x08); - case '9': - return (0x09); - case 'a': - case 'A': - return (0x0a); - case 'b': - case 'B': - return (0x0b); - case 'c': - case 'C': - return (0x0c); - case 'd': - case 'D': - return (0x0d); - case 'e': - case 'E': - return (0x0e); - case 'f': - case 'F': - return (0x0f); - default: - return (-1); - } -} - -/* - * XXX: Review this _carefully_. - */ -static int -login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) -{ - int i, hex_len, nibble; - bool lo = true; /* As opposed to 'hi'. */ - char *bin; - size_t bin_off, bin_len; - - if (strncasecmp(hex, "0x", strlen("0x")) != 0) { - log_warnx("malformed variable, should start with \"0x\""); - return (-1); - } - - hex += strlen("0x"); - hex_len = strlen(hex); - if (hex_len < 1) { - log_warnx("malformed variable; doesn't contain anything " - "but \"0x\""); - return (-1); - } - - bin_len = hex_len / 2 + hex_len % 2; - bin = calloc(bin_len, 1); - if (bin == NULL) - log_err(1, "calloc"); - - bin_off = bin_len - 1; - for (i = hex_len - 1; i >= 0; i--) { - nibble = login_hex2int(hex[i]); - if (nibble < 0) { - log_warnx("malformed variable, invalid char \"%c\"", - hex[i]); - return (-1); - } - - assert(bin_off < bin_len); - if (lo) { - bin[bin_off] = nibble; - lo = false; - } else { - bin[bin_off] |= nibble << 4; - bin_off--; - lo = true; - } - } - - *binp = bin; - *bin_lenp = bin_len; - return (0); -} - -static char * -login_bin2hex(const char *bin, size_t bin_len) -{ - unsigned char *hex, *tmp, ch; - size_t hex_len; - size_t i; - - hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ - hex = malloc(hex_len); - if (hex == NULL) - log_err(1, "malloc"); - - tmp = hex; - tmp += sprintf(tmp, "0x"); - for (i = 0; i < bin_len; i++) { - ch = bin[i]; - tmp += sprintf(tmp, "%02x", ch); - } - - return (hex); -} - static void -login_compute_md5(const char id, const char *secret, - const void *challenge, size_t challenge_len, void *response, - size_t response_len) -{ - MD5_CTX ctx; - int rv; - - assert(response_len == MD5_DIGEST_LENGTH); - - MD5_Init(&ctx); - MD5_Update(&ctx, &id, sizeof(id)); - MD5_Update(&ctx, secret, strlen(secret)); - MD5_Update(&ctx, challenge, challenge_len); - rv = MD5_Final(response, &ctx); - if (rv != 1) - log_errx(1, "MD5_Final"); -} - -static void login_negotiate_key(struct connection *conn, const char *name, const char *value) { int which, tmp; if (strcmp(name, "TargetAlias") == 0) { strlcpy(conn->conn_target_alias, value, sizeof(conn->conn_target_alias)); } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ } else if (strcmp(name, "HeaderDigest") == 0) { which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("target prefers CRC32C " "for header digest; we'll use it"); conn->conn_header_digest = CONN_DIGEST_CRC32C; break; case 2: log_debugx("target prefers not to do " "header digest; we'll comply"); break; default: log_warnx("target sent unrecognized " "HeaderDigest value \"%s\"; will use None", value); break; } } else if (strcmp(name, "DataDigest") == 0) { which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("target prefers CRC32C " "for data digest; we'll use it"); conn->conn_data_digest = CONN_DIGEST_CRC32C; break; case 2: log_debugx("target prefers not to do " "data digest; we'll comply"); break; default: log_warnx("target sent unrecognized " "DataDigest value \"%s\"; will use None", value); break; } } else if (strcmp(name, "MaxConnections") == 0) { /* Ignore. */ } else if (strcmp(name, "InitialR2T") == 0) { if (strcmp(value, "Yes") == 0) conn->conn_initial_r2t = true; else conn->conn_initial_r2t = false; } else if (strcmp(name, "ImmediateData") == 0) { if (strcmp(value, "Yes") == 0) conn->conn_immediate_data = true; else conn->conn_immediate_data = false; } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid " "MaxRecvDataSegmentLength"); conn->conn_max_data_segment_length = tmp; } else if (strcmp(name, "MaxBurstLength") == 0) { if (conn->conn_immediate_data) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid MaxBurstLength"); conn->conn_max_burst_length = tmp; } } else if (strcmp(name, "FirstBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid FirstBurstLength"); conn->conn_first_burst_length = tmp; } else if (strcmp(name, "DefaultTime2Wait") == 0) { /* Ignore */ } else if (strcmp(name, "DefaultTime2Retain") == 0) { /* Ignore */ } else if (strcmp(name, "MaxOutstandingR2T") == 0) { /* Ignore */ } else if (strcmp(name, "DataPDUInOrder") == 0) { /* Ignore */ } else if (strcmp(name, "DataSequenceInOrder") == 0) { /* Ignore */ } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { /* Ignore */ } else if (strcmp(name, "OFMarker") == 0) { /* Ignore */ } else if (strcmp(name, "IFMarker") == 0) { /* Ignore */ } else if (strcmp(name, "TargetPortalGroupTag") == 0) { /* Ignore */ } else { log_debugx("unknown key \"%s\"; ignoring", name); } } static void login_negotiate(struct connection *conn) { struct pdu *request, *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr; int i, nrequests = 0; log_debugx("beginning operational parameter negotiation"); request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); request_keys = keys_new(); /* * The following keys are irrelevant for discovery sessions. */ if (conn->conn_conf.isc_discovery == 0) { if (conn->conn_conf.isc_header_digest != 0) keys_add(request_keys, "HeaderDigest", "CRC32C"); else keys_add(request_keys, "HeaderDigest", "None"); if (conn->conn_conf.isc_data_digest != 0) keys_add(request_keys, "DataDigest", "CRC32C"); else keys_add(request_keys, "DataDigest", "None"); keys_add(request_keys, "ImmediateData", "Yes"); keys_add_int(request_keys, "MaxBurstLength", ISCSI_MAX_DATA_SEGMENT_LENGTH); keys_add_int(request_keys, "FirstBurstLength", ISCSI_MAX_DATA_SEGMENT_LENGTH); keys_add(request_keys, "InitialR2T", "Yes"); } else { keys_add(request_keys, "HeaderDigest", "None"); keys_add(request_keys, "DataDigest", "None"); } keys_add_int(request_keys, "MaxRecvDataSegmentLength", ISCSI_MAX_DATA_SEGMENT_LENGTH); keys_add(request_keys, "DefaultTime2Wait", "0"); keys_add(request_keys, "DefaultTime2Retain", "0"); keys_add(request_keys, "ErrorRecoveryLevel", "0"); keys_add(request_keys, "MaxOutstandingR2T", "1"); keys_save(request_keys, request); keys_delete(request_keys); request_keys = NULL; pdu_send(request); pdu_delete(request); request = NULL; response = login_receive(conn); response_keys = keys_new(); keys_load(response_keys, response); for (i = 0; i < KEYS_MAX; i++) { if (response_keys->keys_names[i] == NULL) break; login_negotiate_key(conn, response_keys->keys_names[i], response_keys->keys_values[i]); } keys_delete(response_keys); response_keys = NULL; for (;;) { bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0) break; nrequests++; if (nrequests > 5) { log_warnx("received login response " "without the \"T\" flag too many times; giving up"); break; } log_debugx("received login response " "without the \"T\" flag; sending another request"); pdu_delete(response); request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); pdu_send(request); pdu_delete(request); response = login_receive(conn); } if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) log_warnx("received final login response with wrong NSG 0x%x", login_nsg(response)); pdu_delete(response); log_debugx("operational parameter negotiation done; " "transitioning to Full Feature phase"); } static void login_send_chap_a(struct connection *conn) { struct pdu *request; struct keys *request_keys; request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); keys_add(request_keys, "CHAP_A", "5"); keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); } static void login_send_chap_r(struct pdu *response) { struct connection *conn; struct pdu *request; struct keys *request_keys, *response_keys; + struct rchap *rchap; const char *chap_a, *chap_c, *chap_i; - char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; - size_t challenge_len; - int error, rv; - unsigned char id; - char *mutual_chap_c, mutual_chap_i[4]; + char *chap_r; + int error; + char *mutual_chap_c, *mutual_chap_i; /* * As in the rest of the initiator, 'request' means * 'initiator -> target', and 'response' means 'target -> initiator', * * So, here the 'response' from the target is the packet that contains * CHAP challenge; our CHAP response goes into 'request'. */ conn = response->pdu_connection; response_keys = keys_new(); keys_load(response_keys, response); /* * First, compute the response. */ chap_a = keys_find(response_keys, "CHAP_A"); if (chap_a == NULL) log_errx(1, "received CHAP packet without CHAP_A"); chap_c = keys_find(response_keys, "CHAP_C"); if (chap_c == NULL) log_errx(1, "received CHAP packet without CHAP_C"); chap_i = keys_find(response_keys, "CHAP_I"); if (chap_i == NULL) log_errx(1, "received CHAP packet without CHAP_I"); - if (strcmp(chap_a, "5") != 0) + if (strcmp(chap_a, "5") != 0) { log_errx(1, "received CHAP packet " "with unsupported CHAP_A \"%s\"", chap_a); - id = strtoul(chap_i, NULL, 10); - error = login_hex2bin(chap_c, &challenge, &challenge_len); - if (error != 0) - log_errx(1, "received CHAP packet with malformed CHAP_C"); - login_compute_md5(id, conn->conn_conf.isc_secret, - challenge, challenge_len, response_bin, sizeof(response_bin)); - free(challenge); - chap_r = login_bin2hex(response_bin, sizeof(response_bin)); + } + rchap = rchap_new(conn->conn_conf.isc_secret); + error = rchap_receive(rchap, chap_i, chap_c); + if (error != 0) { + log_errx(1, "received CHAP packet " + "with malformed CHAP_I or CHAP_C"); + } + chap_r = rchap_get_response(rchap); + rchap_delete(rchap); + keys_delete(response_keys); request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user); keys_add(request_keys, "CHAP_R", chap_r); free(chap_r); /* * If we want mutual authentication, we're expected to send * our CHAP_I/CHAP_C now. */ if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_debugx("requesting mutual authentication; " "binary challenge size is %zd bytes", - sizeof(conn->conn_mutual_challenge)); + sizeof(conn->conn_mutual_chap->chap_challenge)); - rv = RAND_bytes(conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge)); - if (rv != 1) { - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - rv = RAND_bytes(&conn->conn_mutual_id, - sizeof(conn->conn_mutual_id)); - if (rv != 1) { - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge)); - snprintf(mutual_chap_i, sizeof(mutual_chap_i), - "%d", conn->conn_mutual_id); + assert(conn->conn_mutual_chap == NULL); + conn->conn_mutual_chap = chap_new(); + mutual_chap_i = chap_get_id(conn->conn_mutual_chap); + mutual_chap_c = chap_get_challenge(conn->conn_mutual_chap); keys_add(request_keys, "CHAP_I", mutual_chap_i); keys_add(request_keys, "CHAP_C", mutual_chap_c); + free(mutual_chap_i); free(mutual_chap_c); } keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); } static void login_verify_mutual(const struct pdu *response) { struct connection *conn; struct keys *response_keys; const char *chap_n, *chap_r; - char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; - size_t response_bin_len; int error; conn = response->pdu_connection; response_keys = keys_new(); keys_load(response_keys, response); chap_n = keys_find(response_keys, "CHAP_N"); if (chap_n == NULL) log_errx(1, "received CHAP Response PDU without CHAP_N"); chap_r = keys_find(response_keys, "CHAP_R"); if (chap_r == NULL) log_errx(1, "received CHAP Response PDU without CHAP_R"); - error = login_hex2bin(chap_r, &response_bin, &response_bin_len); - if (error != 0) - log_errx(1, "received CHAP Response PDU with malformed CHAP_R"); + error = chap_receive(conn->conn_mutual_chap, chap_r); + if (error != 0) + log_errx(1, "received CHAP Response PDU with invalid CHAP_R"); + if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong user"); } - login_compute_md5(conn->conn_mutual_id, - conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge), expected_response_bin, - sizeof(expected_response_bin)); - - if (memcmp(response_bin, expected_response_bin, - sizeof(expected_response_bin)) != 0) { + error = chap_authenticate(conn->conn_mutual_chap, + conn->conn_conf.isc_mutual_secret); + if (error != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong secret"); } - keys_delete(response_keys); - free(response_bin); + keys_delete(response_keys); + chap_delete(conn->conn_mutual_chap); + conn->conn_mutual_chap = NULL; log_debugx("mutual CHAP authentication succeeded"); } static void login_chap(struct connection *conn) { struct pdu *response; log_debugx("beginning CHAP authentication; sending CHAP_A"); login_send_chap_a(conn); log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); response = login_receive(conn); log_debugx("sending CHAP_N/CHAP_R"); login_send_chap_r(response); pdu_delete(response); /* * XXX: Make sure this is not susceptible to MITM. */ log_debugx("waiting for CHAP result"); response = login_receive(conn); if (conn->conn_conf.isc_mutual_user[0] != '\0') login_verify_mutual(response); pdu_delete(response); log_debugx("CHAP authentication done"); } void login(struct connection *conn) { struct pdu *request, *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr2; const char *auth_method; int i; log_debugx("beginning Login phase; sending Login PDU"); request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); if (conn->conn_conf.isc_mutual_user[0] != '\0') { keys_add(request_keys, "AuthMethod", "CHAP"); } else if (conn->conn_conf.isc_user[0] != '\0') { /* * Give target a chance to skip authentication if it * doesn't feel like it. * * None is first, CHAP second; this is to work around * what seems to be LIO (Linux target) bug: otherwise, * if target is configured with no authentication, * and we are configured to authenticate, the target * will erroneously respond with AuthMethod=CHAP * instead of AuthMethod=None, and will subsequently * fail the connection. This usually happens with * Discovery sessions, which default to no authentication. */ keys_add(request_keys, "AuthMethod", "None,CHAP"); } else { keys_add(request_keys, "AuthMethod", "None"); } keys_add(request_keys, "InitiatorName", conn->conn_conf.isc_initiator); if (conn->conn_conf.isc_initiator_alias[0] != '\0') { keys_add(request_keys, "InitiatorAlias", conn->conn_conf.isc_initiator_alias); } if (conn->conn_conf.isc_discovery == 0) { keys_add(request_keys, "SessionType", "Normal"); keys_add(request_keys, "TargetName", conn->conn_conf.isc_target); } else { keys_add(request_keys, "SessionType", "Discovery"); } keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); response = login_receive(conn); response_keys = keys_new(); keys_load(response_keys, response); for (i = 0; i < KEYS_MAX; i++) { if (response_keys->keys_names[i] == NULL) break; /* * Not interested in AuthMethod at this point; we only need * to parse things such as TargetAlias. * * XXX: This is somewhat ugly. We should have a way to apply * all the keys to the session and use that by default * instead of discarding them. */ if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0) continue; login_negotiate_key(conn, response_keys->keys_names[i], response_keys->keys_values[i]); } bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_errx(1, "target requested transition " "to operational parameter negotiation, " "but we require mutual CHAP"); } log_debugx("target requested transition " "to operational parameter negotiation"); keys_delete(response_keys); pdu_delete(response); login_negotiate(conn); return; } auth_method = keys_find(response_keys, "AuthMethod"); if (auth_method == NULL) log_errx(1, "received response without AuthMethod"); if (strcmp(auth_method, "None") == 0) { if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_errx(1, "target does not require authantication, " "but we require mutual CHAP"); } log_debugx("target does not require authentication"); keys_delete(response_keys); pdu_delete(response); login_negotiate(conn); return; } if (strcmp(auth_method, "CHAP") != 0) { fail(conn, "Unsupported AuthMethod"); log_errx(1, "received response " "with unsupported AuthMethod \"%s\"", auth_method); } if (conn->conn_conf.isc_user[0] == '\0' || conn->conn_conf.isc_secret[0] == '\0') { fail(conn, "Authentication required"); log_errx(1, "target requests CHAP authentication, but we don't " "have user and secret"); } keys_delete(response_keys); response_keys = NULL; pdu_delete(response); response = NULL; login_chap(conn); login_negotiate(conn); }