summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/iscsid.conf (renamed from iscsi.conf)8
-rw-r--r--include/iscsi.h (renamed from iscsi.h)0
-rw-r--r--include/iscsi_proto.h600
-rw-r--r--include/iscsi_u.h34
-rwxr-xr-xiscsiadm988
-rw-r--r--kernel/iscsi_if.h (renamed from iscsi_if.h)0
-rw-r--r--kernel/iscsi_mgr.c (renamed from iscsi_control.c)0
-rw-r--r--kernel/iscsi_mgr.h (renamed from iscsi_control.h)0
-rw-r--r--kernel/iscsi_tcp.c (renamed from iscsi_tcp.c)14
-rw-r--r--kernel/iscsi_tcp.h (renamed from iscsi_tcp.h)0
-rw-r--r--test/regression.dat (renamed from regression.dat)0
-rwxr-xr-xtest/regression.sh (renamed from regression.sh)0
-rw-r--r--usr/Makefile13
-rw-r--r--usr/auth.c2078
-rw-r--r--usr/auth.h285
-rw-r--r--usr/chap.c652
-rw-r--r--usr/ctldev.c89
-rw-r--r--usr/initiator.h151
-rw-r--r--usr/io.c524
-rw-r--r--usr/ipc.c116
-rw-r--r--usr/iscsiadm.c82
-rw-r--r--usr/iscsiadm.h49
-rw-r--r--usr/iscsid.c238
-rw-r--r--usr/iscsid.h50
-rw-r--r--usr/log.c127
-rw-r--r--usr/log.h41
-rw-r--r--usr/login.c1444
-rw-r--r--usr/md5.c236
-rw-r--r--usr/md5.h60
-rw-r--r--usr/sha1.c167
-rw-r--r--usr/sha1.h27
-rw-r--r--usr/types.h16
32 files changed, 7094 insertions, 995 deletions
diff --git a/iscsi.conf b/etc/iscsid.conf
index 572518b..5480e96 100644
--- a/iscsi.conf
+++ b/etc/iscsid.conf
@@ -1,9 +1,9 @@
-initiator_name = iqn.com.dima
-initiator_alias = dima-um
target_name = iqn.2001-04.com.example:storage.disk2.sys1.xyz
target_portal = 10.16.16.227:3260,1
-#target_user = dima
-#target_password = aloha
+login_user = dima
+login_password = aloha
+initiator_name = iqn.com.dima
+initiator_alias = dima-um
isid = 0x80.0x0000.0x00.0x0001
first_burst = 262144
max_recv_dlength = 65536
diff --git a/iscsi.h b/include/iscsi.h
index 2d49bfe..2d49bfe 100644
--- a/iscsi.h
+++ b/include/iscsi.h
diff --git a/include/iscsi_proto.h b/include/iscsi_proto.h
new file mode 100644
index 0000000..a5c1ff9
--- /dev/null
+++ b/include/iscsi_proto.h
@@ -0,0 +1,600 @@
+/*
+ * RFC 3720 (iSCSI) protocol data types
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef ISCSI_PROTO_H
+#define ISCSI_PROTO_H
+
+#define ISCSI_VERSION_STR "0.1.0"
+#define ISCSI_DRAFT20_VERSION 0x00
+
+/* default iSCSI listen port for incoming connections */
+#define ISCSI_LISTEN_PORT 3260
+
+/* Padding word length */
+#define PAD_WORD_LEN 4
+
+/*
+ * useful common(control and data pathes) macro
+ */
+#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2]))
+#define hton24(p, v) { \
+ p[0] = (((v) >> 16) & 0xFF); \
+ p[1] = (((v) >> 8) & 0xFF); \
+ p[2] = ((v) & 0xFF); \
+}
+#define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;}
+
+/*
+ * iSCSI Template Message Header
+ */
+typedef struct iscsi_hdr {
+ uint8_t opcode;
+ uint8_t flags; /* Final bit */
+ uint8_t rsvd2[2];
+ uint8_t hlength; /* AHSs total length */
+ uint8_t dlength[3]; /* Data length */
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Task Tag */
+ uint32_t statsn;
+ uint32_t exp_statsn;
+ uint8_t other[16];
+} iscsi_hdr_t;
+
+/************************* RFC 3720 Begin *****************************/
+
+#define ISCSI_RESERVED_TAG 0xffffffff
+
+/* Opcode encoding bits */
+#define ISCSI_OP_RETRY 0x80
+#define ISCSI_OP_IMMEDIATE 0x40
+#define ISCSI_OPCODE_MASK 0x3F
+
+/* Initiator Opcode values */
+#define ISCSI_OP_NOOP_OUT 0x00
+#define ISCSI_OP_SCSI_CMD 0x01
+#define ISCSI_OP_SCSI_TMFUNC 0x02
+#define ISCSI_OP_LOGIN 0x03
+#define ISCSI_OP_TEXT 0x04
+#define ISCSI_OP_SCSI_DATA_OUT 0x05
+#define ISCSI_OP_LOGOUT 0x06
+#define ISCSI_OP_SNACK 0x10
+
+/* Target Opcode values */
+#define ISCSI_OP_NOOP_IN 0x20
+#define ISCSI_OP_SCSI_CMD_RSP 0x21
+#define ISCSI_OP_SCSI_TMFUNC_RSP 0x22
+#define ISCSI_OP_LOGIN_RSP 0x23
+#define ISCSI_OP_TEXT_RSP 0x24
+#define ISCSI_OP_SCSI_DATA_IN 0x25
+#define ISCSI_OP_LOGOUT_RSP 0x26
+#define ISCSI_OP_R2T 0x31
+#define ISCSI_OP_ASYNC_EVENT 0x32
+#define ISCSI_OP_REJECT_MSG 0x3f
+
+/* SCSI Command Header */
+typedef struct iscsi_cmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2;
+ uint8_t cmdrn;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t data_length;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t cdb[16]; /* SCSI Command Block */
+ /* Additional Data (Command Dependent) */
+} iscsi_cmd_t;
+
+/* Command PDU flags */
+#define ISCSI_FLAG_CMD_FINAL 0x80
+#define ISCSI_FLAG_CMD_READ 0x40
+#define ISCSI_FLAG_CMD_WRITE 0x20
+#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */
+
+/* SCSI Command Attribute values */
+#define ISCSI_ATTR_UNTAGGED 0
+#define ISCSI_ATTR_SIMPLE 1
+#define ISCSI_ATTR_ORDERED 2
+#define ISCSI_ATTR_HEAD_OF_QUEUE 3
+#define ISCSI_ATTR_ACA 4
+
+/* SCSI Response Header */
+typedef struct iscsi_cmd_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response;
+ uint8_t cmd_status;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd1;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t expdatasn;
+ uint32_t residual_count;
+ uint32_t bi_residual_count;
+ /* Response or Sense Data (optional) */
+} iscsi_cmd_rsp_t;
+
+/* Command Response PDU flags */
+#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10
+#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08
+#define ISCSI_FLAG_CMD_OVERFLOW 0x04
+#define ISCSI_FLAG_CMD_UNDERFLOW 0x02
+
+/* iSCSI Status values. Valid if Rsp Selector bit is not set */
+#define ISCSI_STATUS_CMD_COMPLETED 0
+#define ISCSI_STATUS_TARGET_FAILURE 1
+#define ISCSI_STATUS_SUBSYS_FAILURE 2
+
+/* Asynchronous Event Header */
+typedef struct iscsi_async {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint8_t rsvd4[8];
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t async_event;
+ uint8_t async_vcode;
+ uint16_t param1;
+ uint16_t param2;
+ uint16_t param3;
+ uint8_t rsvd5[4];
+} iscsi_async_t;
+
+/* iSCSI Event Indicator values */
+#define ASYNC_EVENT_SCSI_EVENT 0
+#define ASYNC_EVENT_REQUEST_LOGOUT 1
+#define ASYNC_EVENT_DROPPING_CONNECTION 2
+#define ASYNC_EVENT_DROPPING_ALL_CONNECTIONS 3
+#define ASYNC_EVENT_PARAM_NEGOTIATION 4
+#define ASYNC_EVENT_VENDOR_SPECIFIC 255
+
+/* NOP-Out Message */
+typedef struct iscsi_nopout {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd4[16];
+} iscsi_nopout_t;
+
+/* NOP-In Message */
+typedef struct iscsi_nopin {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd4[12];
+} iscsi_nopin_t;
+
+/* SCSI Task Management Message Header */
+typedef struct iscsi_tm {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd1[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rtt; /* Reference Task Tag */
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint32_t refcmdsn;
+ uint32_t expdatasn;
+ uint8_t rsvd2[8];
+} iscsi_tm_t;
+
+#define ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK 0x7F
+
+/* Function values */
+#define ISCSI_TM_FUNC_ABORT_TASK 1
+#define ISCSI_TM_FUNC_ABORT_TASK_SET 2
+#define ISCSI_TM_FUNC_CLEAR_ACA 3
+#define ISCSI_TM_FUNC_CLEAR_TASK_SET 4
+#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5
+#define ISCSI_TM_FUNC_TARGET_WARM_RESET 6
+#define ISCSI_TM_FUNC_TARGET_COLD_RESET 7
+#define ISCSI_TM_FUNC_TASK_REASSIGN 8
+
+/* SCSI Task Management Response Header */
+typedef struct iscsi_tm_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response; /* see Response values below */
+ uint8_t qualifier;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd2[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rtt; /* Reference Task Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd3[12];
+} iscsi_tm_rsp_t;
+
+/* Response values */
+#define SCSI_TCP_TM_RESP_COMPLETE 0x00
+#define SCSI_TCP_TM_RESP_NO_TASK 0x01
+#define SCSI_TCP_TM_RESP_NO_LUN 0x02
+#define SCSI_TCP_TM_RESP_TASK_ALLEGIANT 0x03
+#define SCSI_TCP_TM_RESP_NO_FAILOVER 0x04
+#define SCSI_TCP_TM_RESP_IN_PRGRESS 0x05
+#define SCSI_TCP_TM_RESP_REJECTED 0xff
+
+/* Ready To Transfer Header */
+typedef struct iscsi_r2t_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t r2tsn;
+ uint32_t data_offset;
+ uint32_t data_length;
+} iscsi_r2t_rsp_t;
+
+/* SCSI Data Hdr */
+typedef struct iscsi_data {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t rsvd4;
+ uint32_t exp_statsn;
+ uint32_t rsvd5;
+ uint32_t datasn;
+ uint32_t offset;
+ uint32_t rsvd6;
+ /* Payload */
+} iscsi_data_t;
+
+/* SCSI Data Response Hdr */
+typedef struct iscsi_data_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2;
+ uint8_t cmd_status;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t datasn;
+ uint32_t offset;
+ uint32_t residual_count;
+} iscsi_data_rsp_t;
+
+/* Data Response PDU flags */
+#define ISCSI_FLAG_DATA_ACK 0x40
+#define ISCSI_FLAG_DATA_OVERFLOW 0x04
+#define ISCSI_FLAG_DATA_UNDERFLOW 0x02
+#define ISCSI_FLAG_DATA_STATUS 0x01
+
+/* Text Header */
+typedef struct iscsi_text {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd4[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd5[16];
+ /* Text - key=value pairs */
+} iscsi_text_t;
+
+#define ISCSI_FLAG_TEXT_CONTINUE 0x40
+
+/* Text Response Header */
+typedef struct iscsi_text_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd4[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd5[12];
+ /* Text Response - key:value pairs */
+} iscsi_text_rsp_t;
+
+/* Login Header */
+typedef struct iscsi_login {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t max_version; /* Max. version supported */
+ uint8_t min_version; /* Min. version supported */
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t isid[6]; /* Initiator Session ID */
+ uint16_t tsih; /* Target Session Handle */
+ uint32_t itt; /* Initiator Task Tag */
+ uint16_t cid;
+ uint16_t rsvd3;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd5[16];
+} iscsi_login_t;
+
+/* Login PDU flags */
+#define ISCSI_FLAG_LOGIN_TRANSIT 0x80
+#define ISCSI_FLAG_LOGIN_CONTINUE 0x40
+#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */
+#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */
+
+#define ISCSI_LOGIN_CURRENT_STAGE(flags) \
+((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2)
+#define ISCSI_LOGIN_NEXT_STAGE(flags) \
+(flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK)
+
+/* Login stage (phase) codes for CSG, NSG */
+#define ISCSI_SECURITY_NEGOTIATION_STAGE 0
+#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1
+#define ISCSI_FULL_FEATURE_PHASE 3
+
+/* Login Status response classes */
+#define ISCSI_STATUS_CLS_SUCCESS 0x00
+#define ISCSI_STATUS_CLS_REDIRECT 0x01
+#define ISCSI_STATUS_CLS_INITIATOR_ERR 0x02
+#define ISCSI_STATUS_CLS_TARGET_ERR 0x03
+
+/* Login Status response detail codes */
+/* Class-0 (Success) */
+#define ISCSI_LOGIN_STATUS_ACCEPT 0x00
+
+/* Class-1 (Redirection) */
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02
+
+/* Class-2 (Initiator Error) */
+#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00
+#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01
+#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02
+#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03
+#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04
+#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05
+#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06
+#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07
+#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08
+#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09
+#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a
+#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b
+
+/* Class-3 (Target Error) */
+#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00
+#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01
+#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02
+
+/* Login Response Header */
+typedef struct iscsi_login_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t max_version; /* Max. version supported */
+ uint8_t active_version; /* Active version */
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t isid[6]; /* Initiator Session ID */
+ uint16_t tsih; /* Target Session Handle */
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd3;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t status_class; /* see Login RSP ststus classes below */
+ uint8_t status_detail; /* see Login RSP Status details below */
+ uint8_t rsvd4[10];
+} iscsi_login_rsp_t;
+
+/* Login stage (phase) codes for CSG, NSG */
+#define ISCSI_INITIAL_LOGIN_STAGE -1
+#define ISCSI_SECURITY_NEGOTIATION_STAGE 0
+#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1
+#define ISCSI_FULL_FEATURE_PHASE 3
+
+/* Login Status response classes */
+#define STATUS_CLASS_SUCCESS 0x00
+#define STATUS_CLASS_REDIRECT 0x01
+#define STATUS_CLASS_INITIATOR_ERR 0x02
+#define STATUS_CLASS_TARGET_ERR 0x03
+
+/* Login Status response detail codes */
+/* Class-0 (Success) */
+#define ISCSI_LOGIN_STATUS_ACCEPT 0x00
+
+/* Class-1 (Redirection) */
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02
+
+/* Class-2 (Initiator Error) */
+#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00
+#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01
+#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02
+#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03
+#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04
+#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05
+#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06
+#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07
+#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08
+#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09
+#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a
+#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b
+
+/* Class-3 (Target Error) */
+#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00
+#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01
+#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02
+
+/* Logout Header */
+typedef struct iscsi_logout {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd1[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd2[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint16_t cid;
+ uint8_t rsvd3[2];
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd4[16];
+} iscsi_logout_t;
+
+/* Logout PDU flags */
+#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F
+
+/* logout reason_code values */
+
+#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0
+#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1
+#define ISCSI_LOGOUT_REASON_RECOVERY 2
+#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3
+
+/* Logout Response Header */
+typedef struct iscsi_logout_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response; /* see Logout response values below */
+ uint8_t rsvd2;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd3[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd4;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t rsvd5;
+ uint16_t t2wait;
+ uint16_t t2retain;
+ uint32_t rsvd6;
+} iscsi_logout_rsp_t;
+
+/* logout response status values */
+
+#define ISCSI_LOGOUT_SUCCESS 0
+#define ISCSI_LOGOUT_CID_NOT_FOUND 1
+#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2
+#define ISCSI_LOGOUT_CLEANUP_FAILED 3
+
+/* SNACK Header */
+typedef struct iscsi_snack {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[14];
+ uint32_t itt;
+ uint32_t begrun;
+ uint32_t runlength;
+ uint32_t exp_statsn;
+ uint32_t rsvd3;
+ uint32_t expdatasn;
+ uint8_t rsvd6[8];
+} iscsi_snack_t;
+
+/* SNACK PDU flags */
+#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */
+
+/* Reject Message Header */
+typedef struct iscsi_reject_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t reason;
+ uint8_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t rsvd4[16];
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t datasn;
+ uint8_t rsvd5[8];
+ /* Text - Rejected hdr */
+} iscsi_reject_rsp_t;
+
+/* Reason for Reject */
+#define CMD_BEFORE_LOGIN 1
+#define DATA_DIGEST_ERROR 2
+#define DATA_SNACK_REJECT 3
+#define ISCSI_PROTOCOL_ERROR 4
+#define CMD_NOT_SUPPORTED 5
+#define IMM_CMD_REJECT 6
+#define TASK_IN_PROGRESS 7
+#define INVALID_SNACK 8
+#define BOOKMARK_REJECTED 9
+#define BOOKMARK_NO_RESOURCES 10
+#define NEGOTIATION_RESET 11
+
+/* Max. number of Key=Value pairs in a text message */
+#define MAX_KEY_VALUE_PAIRS 8192
+
+/* maximum length for text keys/values */
+#define KEY_MAXLEN 64
+#define VALUE_MAXLEN 255
+#define TARGET_NAME_MAXLEN VALUE_MAXLEN
+
+#define DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192
+
+/************************* RFC 3720 End *****************************/
+
+#endif /* ISCSI_PROTO_H */
diff --git a/include/iscsi_u.h b/include/iscsi_u.h
new file mode 100644
index 0000000..1f0fe6f
--- /dev/null
+++ b/include/iscsi_u.h
@@ -0,0 +1,34 @@
+/*
+ * iSCSI kernel/user interface
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef ISCSI_U_H
+#define ISCSI_U_H
+
+typedef enum iscsi_uevent_type {
+ ISCSI_UEVENT_UNKNOWN = 0,
+ ISCSI_UEVENT_CONN_FAIL = 1,
+} iscsi_uevent_type_e;
+
+typedef struct iscsi_uevent {
+ uint32_t sid;
+ uint32_t cid;
+ iscsi_uevent_type_e state;
+} iscsi_uevent_t;
+
+#endif /* ISCSI_U_H */
diff --git a/iscsiadm b/iscsiadm
deleted file mode 100755
index 765c1d0..0000000
--- a/iscsiadm
+++ /dev/null
@@ -1,988 +0,0 @@
-#!/usr/bin/perl
-#
-# iSCSI Configuration Utility
-# Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
-# maintained by open-iscsi@@googlegroups.com
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published
-# by the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# See the file COPYING included with this distribution for more details.
-#
-
-use MIME::Base64 qw(decode_base64);
-use Digest::MD5;
-use Cwd;
-use Getopt::Std;
-use File::Basename;
-use Socket;
-use strict;
-
-use vars qw/ %opt /;
-my $reopen_attempts = 0;
-my $reopen_timeout = 3;
-my $recv_timeout = 5;
-my $sysfs_path = "/sys/class/iscsi";
-my $pprefix = "iscsiadm: ";
-
-#
-# Default Configuration
-#
-my $initiator_name = "iqn.com.dima";
-my $initiator_alias = "dima-um";
-my @isid = (0x80, 0x0000, 0x00, 0x0001);
-my $first_burst = 262144;
-my $max_recv_dlength = 65536;
-my $max_burst = 262144;
-my $max_r2t = 1;
-my $max_cnx = 1;
-my $erl = 0;
-my $initial_r2t_en = 0;
-my $imm_data_en = 1;
-my $hdrdgst_en = 0;
-my $datadgst_en = 0;
-my $ifmarker_en = 0;
-my $ofmarker_en = 0;
-my $pdu_inorder_en = 1;
-my $dataseq_inorder_en = 1;
-my $time2wait = 5;
-my $time2retain = 20;
-my $auth_en = 0;
-my $cmdsn = 1;
-my $exp_statsn = 1;
-
-#
-# Response Configuration
-#
-my $target_name;
-my $target_portal;
-my $target_alias;
-my $target_address;
-my $tpgt;
-my $max_xmit_dlength = 8192;
-my $tsih = 0;
-my $exp_cmdsn;
-my $max_cmdsn;
-
-my $target_user;
-my $target_password;
-
-my $iscsiadm_path;
-my ($filename, $dirname) = fileparse($0);
-
-dirname
-
-#
-# path where is looking for rc file
-#
-my @RC_PATH = (
- # actual directory
- "./",
- # home directory
- (getpwuid($>))[7]."/",
- # program directory
- $dirname,
- # parent directory
- "../",
- # default config directory
- "/etc/"
-);
-
-# separator keys and values
-my $RC_SEPARATOR = '=';
-
-# allow for multiple values of a single name
-my $MULTI_VALUES = 1;
-
-sub get_rc {
- my ($rc_name) = @_;
- my %rc_input;
- my ( $key, $value, $rc_file );
-
- if (! -e "$rc_name") {
- foreach ( @RC_PATH ){
- $rc_file="$_"."$rc_name",last if ( -e "$_"."$rc_name");
- }
- } else {
- $rc_file = $rc_name;
- }
-
- fatal("file $rc_name doesn't exist") if ( !$rc_file );
- fatal("can't read file $rc_name") unless ( -r $rc_file || -R $rc_file) ;
-
- open (RC,"$rc_file") || fatal("can't open file $rc_file: $!");
- while (<RC>) {
- chomp;
- # Skip blank text entry fields and comment
- next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/);
-
- # Allow for multiple line values
- if (s/\\$//) {
- $_ .= <RC>;
- redo;
- }
- ($key,$value) = /\s*(.*?)\s*${RC_SEPARATOR}\s*(.*)\s*/;
-
- # skip empty values
- # next if ( !$value || !$key );
-
- # Allow for multiple values of a single name
- if ($rc_input{"$key"} && $MULTI_VALUES) {
- $rc_input{"$key"} .= ", " ;
- $rc_input{"$key"} .= $value;
- } else {
- $rc_input{"$key"} = $value;
- }
- }
- close(RC);
- fatal("can't close file $rc_file: $1") if $?;
- return %rc_input;
-}
-
-sub fatal {
- print "$pprefix"."@_"."\n";
- exit;
-}
-
-$SIG{ALRM} = sub {
- fatal("recv timeout");
-};
-
-sub iscsi_open {
- my ($attr, $op) = @_;
- open(my $fd, "$attr$sysfs_path/$op") ||
- fatal("iSCSI driver is not loaded");
- return $fd;
-}
-
-#
-# Retrieve string with the length of padding filled in
-# by zeroes.
-#
-sub padding {
- my ($dlength) = @_;
- my $pad = "";
- if ($dlength%4 != 0) { $pad = "\0" x (4 - $dlength%4); }
- return $pad;
-}
-
-#
-# Send Login PDU
-#
-sub send_login_req {
- my ($flags, $cid, $data) = @_;
- my $dlength = length($data);
- my $loginpdu = pack('CCCCCCCCCnCnnNnccNNNNNN',
- 0x43, # C: opcode: Login + Immediate
- $flags, # C: flags T, OP, FF
- 0, # C: max ver
- 0, # C: min ver
- 0, # C: hlength
- ($dlength >> 16) & 0xFF, # C: byte 0 of dlength
- ($dlength >> 8) & 0xFF, # C: byte 1 of dlength
- $dlength & 0xFF, # C: byte 2 of dlength
- $isid[0],$isid[1],$isid[2],$isid[3], # CnCn: CID
- $tsih, # n: tsih
- 0, # N: itt
- $cid, # n: cid
- 0,0, # cc: rsvd2
- $cmdsn, # N: cmdsn
- $exp_statsn, # N: exp_statsn
- 0,0,0,0 # NNNN: rsvd16
- );
- print SOCK $loginpdu.$data.padding($dlength);
-}
-
-#
-# Send Text PDU
-#
-sub send_text_req {
- my ($ttt, $data) = @_;
- my $dlength = length($data);
- my $textpdu = pack('CCsCCCCNNNNNNNNNN',
- 0x4, # C: opcode: Text
- 0x80, # C: flags F
- 0, # s: rsvd2
- 0, # C: hlength
- ($dlength >> 16) & 0xFF, # C: byte 0 of dlength
- ($dlength >> 8) & 0xFF, # C: byte 1 of dlength
- $dlength & 0xFF, # C: byte 2 of dlength
- 0, # N: rsvd4
- 0, # N: rsvd4
- 0, # N: itt
- $ttt, # N: ttt
- $cmdsn, # N: cmdsn
- $exp_statsn, # N: exp_statsn
- 0,0,0,0 # NNNN: rsvd16
- );
- print SOCK $textpdu.$data.padding($dlength);
-}
-
-sub recv_pdu {
- my ($len) = @_;
- alarm($recv_timeout);
- read(SOCK, my $rsp, $len)==$len || fatal("bad length response ($len)");
- alarm(0);
- return $rsp;
-}
-
-sub login_text_rsp_parse {
- my ($rsp, $discovery) = @_;
- my $data = "";
-
- my @pairs = split(/\0/, $rsp);
- my $i = 0;
- while (@pairs[$i]) {
- if (@pairs[$i] =~ /^TargetAlias=(.*)/ && !$discovery) {
- $target_alias = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^TargetAddress=(.*)/) {
- $target_address = $1;
- fatal("redirection is not supported");
- } elsif (@pairs[$i] =~ /^TargetPortalGroupTag=(.*)/ &&
- !$discovery) {
- $tpgt = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^InitialR2T=(.*)/) {
- $initial_r2t_en = ($1 eq "Yes"?1:0);
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^ImmediateData=(.*)/) {
- $imm_data_en = ($1 eq "Yes"?1:0);
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^MaxRecvDataSegmentLength=(.*)/) {
- $max_xmit_dlength = $1;
- $data = $data."MaxRecvDataSegmentLength=".
- $max_recv_dlength."\0";
- } elsif (@pairs[$i] =~ /^FirstBurstLength=(.*)/) {
- $first_burst = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^MaxBurstLength=(.*)/) {
- $max_burst = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^HeaderDigest/) {
- $data = $data."HeaderDigest=None"."\0";
- } elsif (@pairs[$i] =~ /^DataDigest/) {
- $data = $data."DataDigest=None"."\0";
- } elsif (@pairs[$i] =~ /^DefaultTime2Wait=(.*)/) {
- $time2wait = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^DefaultTime2Retain=(.*)/) {
- $time2retain = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^MaxOutstandingR2T=(.*)/) {
- $max_r2t = $1;
- $data = $data.@pairs[$i]."\0";
- } elsif (@pairs[$i] =~ /^MaxConnections=(.*)/) {
- fatal("max connections rsp error") if $1 > $max_cnx;
- } elsif (@pairs[$i] =~ /^ErrorRecoveryLevel=(.*)/) {
- fatal("recovery level rsp error")
- if $1 != $erl;
- } elsif (@pairs[$i] =~ /^OFMarker=(.*)/) {
- # ignoring...
- } elsif (@pairs[$i] =~ /^OFMarkInt=(.*)/) {
- # ignoring...
- } elsif (@pairs[$i] =~ /^IFMarker=(.*)/) {
- # ignoring...
- } elsif (@pairs[$i] =~ /^IFMarkInt=(.*)/) {
- # ignoring...
- } elsif (@pairs[$i] =~ /^DataPDUInOrder=(.*)/) {
- # ignoring...
- } elsif (@pairs[$i] =~ /^DataSequenceInOrder=(.*)/) {
- # ignoring...
- }
- $i++;
- }
- return $data;
-}
-
-#
-# Do SendTargets discovery on good result call $callback
-# function which can either automatically login and create new
-# session(s) or just display the result
-#
-sub sendtargets_discovery {
- my ($callback) = @_;
- my $ttt = 0xffffffff;
- my $i = 0;
- my $data = "SendTargets=All\0";
-
- while(1) {
- send_text_req($ttt, $data);
- my $rsp = recv_pdu(48);
- my ($opcode, $flags, $junk, $len0, $len1, $len2, $junk,
- $ttt, $statsn, $junk) = unpack('CCa3CCCa12NNa*', $rsp);
- $opcode == 0x24 || fatal("bad text_rsp opcode ($opcode)");
- my $dlength = (($len0 << 16) | ($len1 << 8) | $len2);
- if ($dlength%4 != 0) { $dlength += 4 - $dlength%4; }
-
- #
- # read & parse Text's DataSegment
- #
- $rsp = recv_pdu($dlength);
- if ($rsp =~ /.*TargetName=(.*)\0TargetAddress=(.*)\0/) {
- $target_name = $1;
- $target_portal = $2;
- } elsif ($rsp =~ /.*TargetAddress=(.*)\0TargetName=(.*)\0/) {
- $target_portal = $1;
- $target_name = $2;
- } else { last; }
-
- &$callback($i++, $target_name, $target_portal);
-
- #
- # stop if T-bit is set
- #
- last if ($flags & 0x80);
-
- $exp_statsn = $statsn + 1;
- $cmdsn++;
- };
-
- close(SOCK);
-}
-
-#
-# Do Login request, auth, parameters negotiation. On success,
-# transfer connection to the kernel.
-#
-sub connection_login {
- my ($target_name, $target_portal, $cid) = @_;
- my $rsp;
- my $dlength;
- my $ipaddr;
- my $port;
- if ($target_portal =~
- /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+),([0-9]+).*$/) {
- $ipaddr = $1;
- $port = $2;
- $tpgt = $3;
- } else {
- print "bad target portal format: $target_portal, ".
- "expecting <ipaddr>:<port>,<tpgt>";
- return 0;
- }
-
- socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp')) ||
- printf($pprefix."socket: $!\n") && return 0;
- setsockopt(SOCK, SOL_SOCKET, SO_SNDBUF, 500*1024);
- setsockopt(SOCK, SOL_SOCKET, SO_RCVBUF, 500*1024);
- connect(SOCK, sockaddr_in($port, inet_aton($ipaddr))) ||
- printf($pprefix."connect: $!\n") && return 0;
- select(SOCK); $| = 1; select(STDOUT); # use unbuffemiles i/o.
-
- my $discovery = $target_name ? 0 : 1;
-
-
- my $data;
- my $data_initial;
- my $flags;
- my $c_stage = -1;
- my $n_stage = -1;
- my $a_stage = -1;
- my $transit = 0;
- my $partial_rsp = 0;
- do {
- if ($c_stage == -1) {
- if (!$discovery) {
- # Normal session
- $data_initial =
- "InitiatorName=$initiator_name\0".
- "InitiatorAlias=$initiator_alias\0".
- "TargetName=$target_name\0".
- "SessionType=Normal" if $cid == 0;
- } else {
- # Discovery session
- $data_initial =
- "InitiatorName=$initiator_name\0".
- "SessionType=Discovery";
- }
- if ($auth_en) {
- # we're prepared to do authentication
- $c_stage = $n_stage = 0;
- } else {
- # can't do any authentication, skip that
- # stage
- $c_stage = $n_stage = 1;
- }
- }
-
- if ($c_stage == 0) {
- # negotiate security...
- if (!$partial_rsp && $auth_en) {
- $data =
- $data_initial."\0AuthMethod=CHAP,None\0";
- }
- } elsif ($c_stage == 1) {
- $n_stage = 3;
- $transit = 1;
- if (!$partial_rsp && $discovery) {
- $data = $data_initial."\0";
- } elsif (!$partial_rsp) {
- $partial_rsp = 1;
- $data = $data_initial."\0".
- "HeaderDigest=".($hdrdgst_en?"CRC32C,None":"None")."\0".
- "DataDigest=".($datadgst_en?"CRC32C,None":"None")."\0".
- "MaxRecvDataSegmentLength=$max_recv_dlength\0".
- "InitialR2T=".($initial_r2t_en?"Yes":"No")."\0".
- "ImmediateData=".($imm_data_en?"Yes":"No")."\0".
- "MaxBurstLength=$max_burst\0".
- "FirstBurstLength=$first_burst\0".
- "DefaultTime2Wait=$time2wait\0".
- "DefaultTime2Retain=$time2retain\0".
- "MaxOutstandingR2T=$max_r2t\0".
- "MaxConnections=$max_cnx\0".
- "ErrorRecoveryLevel=$erl\0".
- "IFMarker=".($ifmarker_en?"Yes":"No")."\0".
- "OFMarker=".($ofmarker_en?"Yes":"No")."\0".
- "DataPDUInOrder=".($pdu_inorder_en?"Yes":"No")."\0".
- "DataSequenceInOrder=".
- ($dataseq_inorder_en?"Yes":"No") if $cid == 0;
- $data =
- "HeaderDigest=".($hdrdgst_en?"CRC32C,None":"None")."\0".
- "DataDigest=".($datadgst_en?"CRC32C,None":"None")."\0".
- "MaxRecvDataSegmentLength=".
- "$max_recv_dlength\0" if $cid != 0;
- }
- }
-
- # fill in the flags
- $flags = 0;
- $flags |= $c_stage << 2;
- if ($transit) {
- # transit to the next stage
- $flags |= $n_stage;
- $flags |= 0x80;
- } else {
- # next == current
- $flags |= $c_stage;
- }
- send_login_req($flags, $cid, $data);
- $rsp = recv_pdu(48);
- my ($opcode, $flags, $vmax, $vactive,
- $junk, $len0, $len1, $len2, $junk, $_tsih, $junk,
- $statsn, $_exp_cmdsn, $_max_cmdsn, $class, $detail, $junk) =
- unpack('CCCCa1CCCa6na8NNNCCa*', $rsp);
- $opcode == 0x23 ||
- printf("bad login_rsp opcode ($opcode)\n") && return 0;
- $dlength = (($len0 << 16) | ($len1 << 8) | $len2);
- if ($dlength%4 != 0) { $dlength += 4 - $dlength%4 };
- $rsp = recv_pdu($dlength);
-
- $class == 1 &&
- printf("redirection is not supported ($detail)\n") &&
- return 0;
- $class == 2 && printf("initiator error ($detail)\n") &&
- return 0;
- $class == 3 && printf("target error ($detail)\n") && return 0;
-
- $vactive != 0 && printf("version mismatch") && return 0;
-
- # make sure the current stages matches
- $c_stage != (($flags & 0x0C)>>2) &&
- printf("protocol error: current stage not matches\n") &&
- return 0;
-
- # make sure that we're actually advancing if the
- # T-bit is set
- $transit && (($flags & 0x03) <= $c_stage) && !$discovery &&
- printf("protocol error: next stage is not advancing\n") &&
- return 0;
-
- if ($transit && ($flags & 0x03) == 0x03) {
- # advance to the next stage
- $partial_rsp = 0;
- $c_stage = $flags & 0x3;
- } else {
- # we got a partial response, don't advance,
- # more negotiation to do
- $partial_rsp = 1;
- }
-
- if ($c_stage == 0) {
- my @pairs = split(/\0/, $rsp);
- my $i = 0;
- my $chap_id = -1;
- my $chap_ch = "";
-
- $data = "";
-
- while (@pairs[$i]) {
- if ($a_stage < 2 &&
- @pairs[$i] =~ /.*CHAP_I=(.*)/) {
- $chap_id = $1;
- $a_stage++;
- } elsif ($a_stage < 2 &&
- @pairs[$i] =~ /.*CHAP_C=(.*)/) {
- $chap_ch = $1;
- $a_stage++;
- } elsif ($a_stage < 0 &&
- @pairs[$i] =~ /.*AuthMethod=(.*)/) {
- if ($1 eq "CHAP") {
- $data = $data."CHAP_A=5\0";
- $a_stage = 0;
- } elsif ($1 eq "None") {
- $n_stage = 1;
- $transit = 1;
- $data = $data.@pairs[$i]."\0";
- }
- } elsif ($a_stage < 0) {
- $data = $data.@pairs[$i]."\0";
- }
- $i++
- }
- if ($a_stage == 2) {
- my $md5;
-
- $chap_ch =~ s/["']//g;
- if ($chap_ch =~ s/^0x(.*)//) {
- $chap_ch = pack("H*", lc($1));
- } elsif ($chap_ch =~ s/^0[Bb](.*)//) {
- $chap_ch = decode_base64($chap_ch);
- } else {
- printf("protocol error: ".
- "wrong challenge format\n");
- return 0;
- }
- $chap_id =~ s/["']//g;
-
- $md5 = new Digest::MD5;
- $md5->reset;
- $md5->add(pack("C", $chap_id));
- $md5->add($target_password);
- $md5->add(pack("a*", $chap_ch));
- $data = $data."CHAP_N=$target_user\0";
- $data = $data."CHAP_R=0x".
- $md5->hexdigest()."\0";
-
- $n_stage = 1;
- $transit = 1;
- $a_stage++;
- }
- } elsif ($c_stage == 1) {
- $data = login_text_rsp_parse($rsp, $discovery);
- }
-
- # record some fields for later use
- $tsih = $_tsih;
- $max_cmdsn = $_max_cmdsn;
- $exp_cmdsn = $_exp_cmdsn;
- $exp_statsn = $statsn + 1;
- } while ($c_stage != 3);
-
- return fileno(SOCK);
-}
-
-sub get_session_ids {
- opendir(DIR, $sysfs_path);
- my @ids = grep(s/^host([0-9]+)$/\1/,readdir(DIR));
- closedir(DIR);
- return @ids;
-}
-
-sub getstate {
- my ($id) = @_;
- my $fd;
-
- $fd = iscsi_open("<", "host$id/state");
- chop(my @state = <$fd>);
- close($fd);
- return $state[0];
-}
-
-sub getparam {
- my ($id, $param) = @_;
- my $fd;
-
- if ($id == -1) {
- $fd = iscsi_open("<", "initiator_parameters");
- } else {
- $fd = iscsi_open("<", "host$id/parameters");
- }
- my @lines = <$fd>;
- close($fd);
- foreach my $line (@lines) {
- if ($line =~ /^$param.*=(.*)$/) {
- $line = $1;
- $line =~ s/^\s+|\s+$//g;
- return $line;
- }
- }
- fatal("not existing parameter '$param'!");
-}
-
-sub show_sessions {
- my @ids = get_session_ids();
-
- if (length(@ids) == 0) {
- printf "Active iSCSI Sessions: none\n";
- } else {
- printf "Active iSCSI Sessions:\n";
- foreach my $id (sort @ids) {
- my $state = getstate($id);
- printf("#%d %s ($state)\n",
- $id, getparam($id, "target_name"));
- }
- }
-}
-
-sub setparam {
- my ($id, $param, $value) = @_;
- my $fd;
-
- if ($id == -1) {
- $fd = iscsi_open(">", "initiator_parameters");
- } else {
- $fd = iscsi_open(">", "host$id/parameters");
- }
- print $fd "$param $value";
- close($fd);
-}
-
-sub operation {
- my ($str) = @_;
- my $fd = iscsi_open(">", "session_operation");
- if (syswrite($fd, "$str") == 0) {
- close($fd);
- printf("iscsiadm: unsuccessful operation: '$str'\n");
- return 0;
- }
- close($fd);
- return 1;
-}
-
-sub logout {
- my ($sid) = @_;
- operation("tcp connection remove $sid 0") || exit 1;
- operation("tcp session remove $sid") || exit 1;
-}
-
-sub read_isid {
- my ($in_isid) = @_;
- my @out_isid;
-
- if ($in_isid =~
- /(0x)?([0-9a-fA-F]+)\.(0x)?([0-9a-fA-F]+)\.(0x)?([0-9a-fA-F]+)\.(0x)?([0-9a-fA-F]+)/) {
- $out_isid[0] = $1 ? hex($2) : $2+0;
- $out_isid[1] = $3 ? hex($4) : $4+0;
- $out_isid[2] = $5 ? hex($6) : $6+0;
- $out_isid[3] = $7 ? hex($8) : $8+0;
- } else {
- fatal "can not recognize ISID format! ".
- "Expecting 'A.B.C.D', got '$in_isid'";
- }
-
- return @out_isid;
-}
-
-sub sysfs_save {
- setparam(-1, "initiator_name", $initiator_name);
- setparam(-1, "initiator_alias", $initiator_alias);
- setparam(-1, "isid", @isid);
- setparam(-1, "target_name", $target_name);
- setparam(-1, "target_alias", $target_name);
- setparam(-1, "target_portal", $target_portal);
- setparam(-1, "target_address", $target_portal);
- setparam(-1, "tpgt", $tpgt);
- setparam(-1, "first_burst", $first_burst);
- setparam(-1, "max_recv_dlength", $max_recv_dlength);
- setparam(-1, "max_xmit_dlength", $max_xmit_dlength);
- setparam(-1, "max_burst", $max_burst);
- setparam(-1, "max_r2t", $max_r2t);
- setparam(-1, "max_cnx", $max_cnx);
- setparam(-1, "erl", $erl);
- setparam(-1, "initial_r2t_en", $initial_r2t_en);
- setparam(-1, "imm_data_en", $imm_data_en);
- setparam(-1, "hdrdgst_en", $hdrdgst_en);
- setparam(-1, "datadgst_en", $datadgst_en);
- setparam(-1, "ifmarker_en", $ifmarker_en);
- setparam(-1, "ofmarker_en", $ofmarker_en);
- setparam(-1, "pdu_inorder_en", $pdu_inorder_en);
- setparam(-1, "dataseq_inorder_en", $dataseq_inorder_en);
- setparam(-1, "time2wait", $time2wait);
- setparam(-1, "time2retain", $time2retain);
- setparam(-1, "auth_en", $auth_en);
- setparam(-1, "cmdsn", $cmdsn);
- setparam(-1, "exp_cmdsn", $exp_cmdsn);
- setparam(-1, "max_cmdsn", $max_cmdsn);
- setparam(-1, "tsih", $tsih);
-}
-
-sub sysfs_restore {
- my ($id) = @_;
-
- $initiator_name = getparam(-1, "initiator_name");
- $initiator_alias = getparam(-1, "initiator_alias");
- @isid = read_isid(getparam($id, "isid"));
- $tsih = getparam($id, "tsih");
- $target_name = getparam($id, "target_name");
- $target_alias = getparam($id, "target_alias");
- $target_portal = getparam($id, "target_portal");
- $target_address = getparam($id, "target_address");
- $tpgt = getparam($id, "tpgt");
- $first_burst = getparam($id, "first_burst");
- $max_burst = getparam($id, "max_burst");
- $max_r2t = getparam($id, "max_r2t");
- $max_cnx = getparam($id, "max_cnx");
- $erl = getparam($id, "erl");
- $initial_r2t_en = getparam($id, "initial_r2t_en");
- $imm_data_en = getparam($id, "imm_data_en");
- $ifmarker_en = getparam($id, "ifmarker_en");
- $ofmarker_en = getparam($id, "ofmarker_en");
- $pdu_inorder_en = getparam($id, "pdu_inorder_en");
- $dataseq_inorder_en = getparam($id, "dataseq_inorder_en");
- $time2wait = getparam($id, "time2wait");
- $time2retain = getparam($id, "time2retain");
-
- #$max_recv_dlength = getparam($id, "max_recv_dlength");
- #$max_xmit_dlength = getparam($id, "max_xmit_dlength");
- #$hdrdgst_en = getparam($id, "hdrdgst_en");
- #$datadgst_en = getparam($id, "datadgst_en");
-}
-
-sub add_conn {
- my ($sid) = @_;
-}
-
-############################## end of library #################################
-
-sub config_read {
- my ($file) = @_;
-
- my %conf = get_rc($file);
- $target_name = $conf{target_name};
- $target_portal = $conf{target_portal};
- $target_user = $conf{target_user};
- $auth_en = 1 if defined($target_user);
- $target_password = $conf{target_password};
- $initiator_name = $conf{initiator_name};
- $initiator_alias = $conf{initiator_alias};
- @isid = read_isid($conf{isid});
- $first_burst = $conf{first_burst};
- $max_recv_dlength = $conf{max_recv_dlength};
- $max_burst = $conf{max_burst};
- $max_r2t = $conf{max_r2t};
- $max_cnx = $conf{max_cnx};
- $erl = $conf{erl};
- $initial_r2t_en = $conf{initial_r2t_en};
- $imm_data_en = $conf{imm_data_en};
- $hdrdgst_en = $conf{hdrdgst_en};
- $datadgst_en = $conf{datadgst_en};
- $ifmarker_en = $conf{ifmarker_en};
- $ofmarker_en = $conf{ofmarker_en};
- $pdu_inorder_en = $conf{pdu_inorder_en};
- $dataseq_inorder_en = $conf{dataseq_inorder_en};
- $time2wait = $conf{time2wait};
- $time2retain = $conf{time2retain};
-
- # sanity check
- if ($max_burst < $first_burst) {
- $max_burst = $first_burst;
- }
-}
-
-sub discovery_list_cb {
- my ($id, $target_name, $target_portal) = @_;
- printf "#%d %s [%s]\n", $id, $target_name, $target_portal;
-}
-
-sub discovery_login_cb {
- my ($id, $target_name, $target_portal) = @_;
- my $cnx_cnt = 0;
- my $sid;
-
- # reset TSIH after Discovery
- $tsih = 0;
-
- do {
- # re-negotiate $max_xmit_dlength for each connection
- # individually.
- $max_xmit_dlength = 8192;
- my $sock_fd = connection_login($target_name,
- $target_portal, $cnx_cnt);
- if ($sock_fd) {
- if ($cnx_cnt == 0) { # leading connection
- sysfs_save();
- my @sids = get_session_ids();
- my ($min,$max)=(sort @sids)[0,-1];
- $sid = ++$max;
- operation("tcp session add $sid") || exit 1;
- }
- operation("tcp connection add $sid ".
- "$cnx_cnt $sock_fd") || exit 1;
- require 'sys/syscall.ph';
- syscall(&SYS_close, $sock_fd + 0);
- }
- } while (++$cnx_cnt < $max_cnx);
-}
-
-sub try_reopen {
- my ($id) = @_;
- my $sock_fd;
-
- sysfs_restore($id);
- $sock_fd = connection_login($target_name, $target_portal, 0);
- if ($sock_fd) {
- sysfs_save();
- my $res = operation("tcp connection add $id 0 $sock_fd");
- require 'sys/syscall.ph';
- syscall(&SYS_close, $sock_fd + 0);
- return $res;
- }
- return 0;
-}
-
-sub usage {
-print STDERR << "EOF";
-
-iSCSI Configuration Utility
-
- usage: iscsiadm [-hval] [-f file] [-d addr:port] [-r id] [-c id]
-
- -h : this (help) message
- -v : verbose output
- -f file : file containing configuration, using /etc/iscsi.conf
- if omited
- -d addr:port : SendTargets method IP-address and port
- -c sid:cid : Add one more connection 'cid' to the session 'sid'
- -r sid[:cid] : Logout and remove iSCSI session specified by 'sid'
- and all connections. Or remove only connection
- specified by 'cid'
- -a : process hotplug events
- -l : initiate login phase for all targets described via
- configuration
-
- example: iscsiadm -f /mypath/my.conf 127.0.0.1:3260
-
-EOF
-exit;
-}
-
-sub init() {
- my $opt_string = 'hvr:d:f:alc:';
- getopts( "$opt_string", \%opt ) or usage();
- usage() if $opt{h};
- $iscsiadm_path = $0;
- if ($iscsiadm_path =~ /^\.(.*)$/) {
- $iscsiadm_path = cwd . $1;
- }
-}
-
-sub hotplug_agent_check() {
- if (! -x "/etc/hotplug/iscsi.agent") {
- open(FH,">/etc/hotplug/iscsi.agent");
- print FH "#!/bin/sh
-#
-# Generated by iscsiadm
-#
-# iSCSI hotplug agent for 2.6 kernels
-#
-# Example of event:
-#
-# PHYSDEVPATH=/devices/platform/host23
-# SUBSYSTEM=iscsi
-# DEVPATH=/class/iscsi/host2
-# PATH=/sbin:/bin:/usr/sbin:/usr/bin
-# ACTION=change
-#
-
-cd /etc/hotplug
-. ./hotplug.functions
-
-ISCSIADM=$iscsiadm_path
-
-case \$ACTION in
-
-change)
- \$ISCSIADM -a
- ;;
-
-*)
- debug_mesg \"iSCSI \$ACTION event not supported\"
- exit 1
- ;;
-
-esac
-";
- close(FH);
- chmod(0755, "/etc/hotplug/iscsi.agent");
- }
-}
-
-sub hotplug_event() {
- if ($ENV{"SUBSYSTEM"} eq "iscsi" &&
- $ENV{"ACTION"} eq "change" &&
- $ENV{"DEVPATH"} =~ /^\/class\/iscsi\/host([0-9]+)$/) {
- my $cnt = $reopen_attempts;
- while($cnt) {
- last if try_reopen($1);
- sleep($reopen_timeout);
- --$cnt if $cnt != -1;
- }
- exit 0;
- } else {
- fatal("not recognized hotplug event!");
- }
-}
-
-################################ main program ################################
-
-init();
-hotplug_agent_check();
-if (defined($opt{f})) {
- config_read($opt{f});
-} else {
- config_read("iscsi.conf");
-}
-if ($opt{a}) {
- hotplug_event();
-}
-if (defined($opt{r})) {
- logout($opt{r});
- exit;
-}
-if (defined($opt{c})) {
- add_conn($opt{c});
- exit;
-}
-if ($opt{v}) {
- if ($opt{d}) {
- #
- # Do SendTargets method discovery (verbose)
- #
- my $sock_fd = connection_login("", $opt{d}.",1", 0);
- if ($sock_fd) {
- sendtargets_discovery(\&discovery_list_cb, $opt{d});
- require 'sys/syscall.ph';
- syscall(&SYS_close, $sock_fd + 0);
- }
- exit;
- } else {
- usage();
- }
- exit;
-} else {
- if ($opt{d}) {
- #
- # Do SendTargets method discovery
- #
- my $sock_fd = connection_login("", $opt{d}.",1", 0);
- if ($sock_fd) {
- sendtargets_discovery(\&discovery_login_cb, $opt{d});
- require 'sys/syscall.ph';
- syscall(&SYS_close, $sock_fd + 0);
- }
- exit;
- }
-}
-if ($opt{l}) {
- discovery_login_cb(0, $target_name, $target_portal);
- exit;
-}
-show_sessions();
diff --git a/iscsi_if.h b/kernel/iscsi_if.h
index 4d2e83b..4d2e83b 100644
--- a/iscsi_if.h
+++ b/kernel/iscsi_if.h
diff --git a/iscsi_control.c b/kernel/iscsi_mgr.c
index c8547bc..c8547bc 100644
--- a/iscsi_control.c
+++ b/kernel/iscsi_mgr.c
diff --git a/iscsi_control.h b/kernel/iscsi_mgr.h
index 2fd6e90..2fd6e90 100644
--- a/iscsi_control.h
+++ b/kernel/iscsi_mgr.h
diff --git a/iscsi_tcp.c b/kernel/iscsi_tcp.c
index a60a40f..2816e71 100644
--- a/iscsi_tcp.c
+++ b/kernel/iscsi_tcp.c
@@ -91,9 +91,6 @@ static int iscsi_proc_info(struct Scsi_Host *host, char *buffer, char **start,
* *
******************************************************************************/
-/*
- * Insert before consumer pointer
- */
static void
__insert(iscsi_queue_t *queue, void *data)
{
@@ -108,6 +105,17 @@ __insert(iscsi_queue_t *queue, void *data)
queue->pool[--queue->cons] = data;
}
+/*
+ * Insert before consumer pointer
+ */
+static void
+iscsi_insert(iscsi_queue_t *queue, void *data)
+{
+ spin_lock_bh(queue->lock);
+ __insert(queue, data);
+ spin_unlock_bh(queue->lock);
+}
+
static void
__enqueue(iscsi_queue_t *queue, void *data)
{
diff --git a/iscsi_tcp.h b/kernel/iscsi_tcp.h
index a736eff..a736eff 100644
--- a/iscsi_tcp.h
+++ b/kernel/iscsi_tcp.h
diff --git a/regression.dat b/test/regression.dat
index 15cf282..15cf282 100644
--- a/regression.dat
+++ b/test/regression.dat
diff --git a/regression.sh b/test/regression.sh
index 066490b..066490b 100755
--- a/regression.sh
+++ b/test/regression.sh
diff --git a/usr/Makefile b/usr/Makefile
new file mode 100644
index 0000000..77e4b47
--- /dev/null
+++ b/usr/Makefile
@@ -0,0 +1,13 @@
+CFLAGS += -O2 -fno-inline -Wall -Wstrict-prototypes -g -I../include
+PROGRAMS = iscsid iscsiadm
+
+all: $(PROGRAMS)
+
+iscsid: io.o auth.o login.o iscsid.o ctldev.o log.o md5.o sha1.o ipc.o
+ $(CC) $^ -o $@
+
+iscsiadm: iscsiadm.o ctldev.o
+ $(CC) $^ -o $@
+
+clean:
+ rm -f *.o $(PROGRAMS)
diff --git a/usr/auth.c b/usr/auth.c
new file mode 100644
index 0000000..1411e45
--- /dev/null
+++ b/usr/auth.c
@@ -0,0 +1,2078 @@
+/*
+ * iSCSI Authorization Library
+ *
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * Originally based on:
+ * Copyright (C) 2001 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * This file implements the iSCSI CHAP authentication method based on
+ * RFC 3720. The code in this file is meant to be common for both kernel and
+ * user level and makes use of only limited library functions, presently only
+ * string.h. Routines specific to kernel, user level are implemented in
+ * seperate files under the appropriate directories.
+ * This code in this files assumes a single thread of execution
+ * for each iscsi_acl structure, and does no locking.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "auth.h"
+#include "initiator.h"
+#include "md5.h"
+#include "log.h"
+
+static const char acl_hexstring[] = "0123456789abcdefABCDEF";
+static const char acl_base64_string[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char acl_authmethod_set_chap_alg_list[] = "CHAP";
+static const char acl_reject_option_name[] = "Reject";
+
+void auth_md5_init(struct MD5Context *);
+void auth_md5_update(struct MD5Context *, unsigned char *, unsigned int);
+void auth_md5_final(unsigned char *, struct MD5Context *);
+void get_random_bytes(unsigned char *data, unsigned int length);
+size_t strlcpy(char *, const char *, size_t);
+size_t strlcat(char *, const char *, size_t);
+
+enum auth_dbg_status
+acl_chap_compute_rsp(struct iscsi_acl *client, int rmt_auth, unsigned int id,
+ unsigned char *challenge_data,
+ unsigned int challenge_length,
+ unsigned char *response_data)
+{
+ unsigned char id_data[1];
+ struct MD5Context context;
+ unsigned char out_data[AUTH_STR_MAX_LEN];
+ unsigned int out_length = AUTH_STR_MAX_LEN;
+
+ if (!client->passwd_present)
+ return AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET;
+
+ auth_md5_init(&context);
+
+ /* id byte */
+ id_data[0] = id;
+ auth_md5_update(&context, id_data, 1);
+
+ /* decrypt password */
+ if (acl_data(out_data, &out_length, client->passwd_data,
+ client->passwd_length))
+ return AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED;
+
+ if (!rmt_auth && !client->ip_sec && out_length < 12)
+ return AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC;
+
+ /* shared secret */
+ auth_md5_update(&context, out_data, out_length);
+
+ /* clear decrypted password */
+ memset(out_data, 0, AUTH_STR_MAX_LEN);
+
+ /* challenge value */
+ auth_md5_update(&context, challenge_data, challenge_length);
+
+ auth_md5_final(response_data, &context);
+
+ return AUTH_DBG_STATUS_NOT_SET; /* no error */
+}
+
+/*
+ * Authenticate a target's CHAP response.
+ */
+int
+acl_chap_auth_request(struct iscsi_acl *client, char *username, unsigned int id,
+ unsigned char *challenge_data,
+ unsigned int challenge_length,
+ unsigned char *response_data,
+ unsigned int rsp_length)
+{
+ struct iscsi_session *session = client->session_handle;
+ struct MD5Context context;
+ unsigned char verify_data[16];
+
+ /* the expected credentials are in the session */
+ if (session->username_in == NULL) {
+ log_error("failing authentication, no incoming username "
+ "configured to authenticate target %s\n",
+ session->target_name);
+ return AUTH_STATUS_FAIL;
+ }
+ if (strcmp(username, session->username_in) != 0) {
+ log_error("failing authentication, received incorrect "
+ "username from target %s\n", session->target_name);
+ return AUTH_STATUS_FAIL;
+ }
+
+ if ((session->password_length_in < 1) ||
+ (session->password_in == NULL) ||
+ (session->password_in[0] == '\0')) {
+ log_error("failing authentication, no incoming password "
+ "configured to authenticate target %s\n",
+ session->target_name);
+ return AUTH_STATUS_FAIL;
+ }
+
+ /* challenge length is I->T, and shouldn't need to be checked */
+
+ if (rsp_length != sizeof(verify_data)) {
+ log_error("failing authentication, received incorrect "
+ "CHAP response length %u from target %s\n",
+ rsp_length, session->target_name);
+ return AUTH_STATUS_FAIL;
+ }
+
+ auth_md5_init(&context);
+
+ /* id byte */
+ verify_data[0] = id;
+ auth_md5_update(&context, verify_data, 1);
+
+ /* shared secret */
+ auth_md5_update(&context, (unsigned char *)session->password_in,
+ session->password_length_in);
+
+ /* challenge value */
+ auth_md5_update(&context, (unsigned char *)challenge_data,
+ challenge_length);
+
+ auth_md5_final(verify_data, &context);
+
+ if (memcmp(response_data, verify_data, sizeof(verify_data)) == 0) {
+ log_debug(1, "initiator authenticated target %s\n",
+ session->target_name);
+ return AUTH_STATUS_PASS;
+ }
+
+ log_error("failing authentication, received incorrect CHAP "
+ "response from target %s\n", session->target_name);
+ return AUTH_STATUS_FAIL;
+}
+
+void
+auth_md5_init(struct MD5Context *context)
+{
+ MD5Init(context);
+}
+
+void
+auth_md5_update(struct MD5Context *context, unsigned char *data,
+ unsigned int length)
+{
+ MD5Update(context, data, length);
+}
+
+void
+auth_md5_final(unsigned char *hash, struct MD5Context *context)
+{
+ MD5Final(hash, context);
+}
+
+void
+get_random_bytes(unsigned char *data, unsigned int length)
+{
+
+ long r;
+ unsigned n;
+
+ while (length > 0) {
+
+ r = rand();
+ r = r ^ (r >> 8);
+ r = r ^ (r >> 4);
+ n = r & 0x7;
+
+ r = rand();
+ r = r ^ (r >> 8);
+ r = r ^ (r >> 5);
+ n = (n << 3) | (r & 0x7);
+
+ r = rand();
+ r = r ^ (r >> 8);
+ r = r ^ (r >> 5);
+ n = (n << 2) | (r & 0x3);
+
+ *data++ = n;
+ length--;
+ }
+}
+
+/**
+ * strlcpy - Copy a %NUL terminated string into a sized buffer
+ * @dest: Where to copy the string to
+ * @src: Where to copy the string from
+ * @size: size of destination buffer
+ *
+ * Compatible with *BSD: the result is always a valid
+ * NUL-terminated string that fits in the buffer (unless,
+ * of course, the buffer size is zero). It does not pad
+ * out the result like strncpy() does.
+ **/
+
+size_t strlcpy(char *dest, const char *src, size_t size)
+{
+ size_t ret = strlen(src);
+
+ if (size) {
+ size_t len = (ret >= size) ? size-1 : ret;
+ memcpy(dest, src, len);
+ dest[len] = '\0';
+ }
+ return ret;
+}
+
+/**
+ * strlcat - Append a length-limited, %NUL-terminated string to another
+ * @dest: The string to be appended to
+ * @src: The string to append to it
+ * @count: The size of the destination buffer.
+ **/
+
+size_t strlcat(char *dest, const char *src, size_t count)
+{
+ size_t dsize = strlen(dest);
+ size_t len = strlen(src);
+ size_t res = dsize + len;
+
+ /* This would be a bug */
+ if (dsize >= count) {
+ dest[count - 1] = 0;
+ return count;
+ }
+
+ dest += dsize;
+ count -= dsize;
+ if (len >= count)
+ len = count-1;
+ memcpy(dest, src, len);
+ dest[len] = 0;
+ return res;
+}
+
+static const char acl_none_option_name[] = "None";
+
+static int
+acl_text_to_number(const char *text, unsigned long *num)
+{
+ char *end;
+ unsigned long number = *num;
+
+ if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X'))
+ number = strtoul(text + 2, &end, 16);
+ else
+ number = strtoul(text, &end, 10);
+
+ if (*text != '\0' && *end == '\0') {
+ *num = number;
+ return 0; /* No error */
+ } else
+ return 1; /* Error */
+}
+
+static int
+acl_chk_string(const char *s, unsigned int max_len, unsigned int *out_len)
+{
+ unsigned int len;
+
+ if (!s)
+ return 1;
+
+ for (len = 0; len < max_len; len++)
+ if (*s++ == '\0') {
+ if (out_len)
+ *out_len = len;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+acl_str_index(const char *s, int c)
+{
+ char *str = strchr(s, c);
+
+ if (str)
+ return (str - s);
+ else
+ return -1;
+}
+
+static int
+acl_chk_auth_mthd_optn(int val)
+{
+ if (val == AUTH_OPTION_NONE || val == AUTH_METHOD_CHAP)
+ return 0;
+
+ return 1;
+}
+
+static const char *
+acl_authmethod_optn_to_text(int value)
+{
+ const char *s;
+ switch (value) {
+ case AUTH_OPTION_REJECT:
+ s = acl_reject_option_name;
+ break;
+ case AUTH_OPTION_NONE:
+ s = acl_none_option_name;
+ break;
+ case AUTH_METHOD_CHAP:
+ s = acl_authmethod_set_chap_alg_list;
+ break;
+ default:
+ s = 0;
+ }
+ return s;
+}
+
+static int
+acl_chk_chap_alg_optn(int chap_algorithm)
+{
+ if (chap_algorithm == AUTH_OPTION_NONE ||
+ chap_algorithm == AUTH_CHAP_ALG_MD5)
+ return 0;
+
+ return 1;
+}
+
+static int
+acl_data_to_text(unsigned char *data, unsigned int data_length, char *text,
+ unsigned int text_length)
+{
+ unsigned long n;
+
+ if (!text || text_length == 0)
+ return 1;
+
+ if (!data || data_length == 0) {
+ *text = '\0';
+ return 1;
+ }
+
+ if (text_length < 3) {
+ *text = '\0';
+ return 1;
+ }
+
+ *text++ = '0';
+ *text++ = 'x';
+
+ text_length -= 2;
+
+ while (data_length > 0) {
+
+ if (text_length < 3) {
+ *text = '\0';
+ return 1;
+ }
+
+ n = *data++;
+ data_length--;
+
+ *text++ = acl_hexstring[(n >> 4) & 0xf];
+ *text++ = acl_hexstring[n & 0xf];
+
+ text_length -= 2;
+ }
+
+ *text = '\0';
+
+ return 0;
+}
+
+static int
+acl_hex_to_data(const char *text, unsigned int text_length, unsigned char *data,
+ unsigned int *data_lenp)
+{
+ int i;
+ unsigned int n1;
+ unsigned int n2;
+ unsigned int data_length = *data_lenp;
+
+ if ((text_length % 2) == 1) {
+
+ i = acl_str_index(acl_hexstring, *text++);
+ if (i < 0)
+ return 1; /* error, bad character */
+
+ if (i > 15)
+ i -= 6;
+ n2 = i;
+
+ if (data_length < 1)
+ return 1; /* error, too much data */
+
+ *data++ = n2;
+ data_length--;
+ }
+
+ while (*text != '\0') {
+ i = acl_str_index(acl_hexstring, *text++);
+ if (i < 0)
+ return 1; /* error, bad character */
+
+ if (i > 15)
+ i -= 6;
+ n1 = i;
+
+ if (*text == '\0')
+ return 1; /* error, odd string length */
+
+ i = acl_str_index(acl_hexstring, *text++);
+ if (i < 0)
+ return 1; /* error, bad character */
+
+ if (i > 15)
+ i -= 6;
+ n2 = i;
+
+ if (data_length < 1)
+ return 1; /* error, too much data */
+
+ *data++ = (n1 << 4) | n2;
+ data_length--;
+ }
+
+ if (data_length >= *data_lenp)
+ return 1; /* error, no data */
+
+ *data_lenp = *data_lenp - data_length;
+
+ return 0; /* no error */
+}
+
+static int
+acl_base64_to_data(const char *text, unsigned char *data,
+ unsigned int *data_lenp)
+{
+ int i;
+ unsigned int n;
+ unsigned int count;
+ unsigned int data_length = *data_lenp;
+
+ n = 0;
+ count = 0;
+
+ while (*text != '\0' && *text != '=') {
+
+ i = acl_str_index(acl_base64_string, *text++);
+ if (i < 0)
+ return 1; /* error, bad character */
+
+ n = (n << 6 | (unsigned int)i);
+ count++;
+
+ if (count >= 4) {
+ if (data_length < 3)
+ return 1; /* error, too much data */
+ *data++ = n >> 16;
+ *data++ = n >> 8;
+ *data++ = n;
+ data_length -= 3;
+ n = 0;
+ count = 0;
+ }
+ }
+
+ while (*text != '\0')
+ if (*text++ != '=')
+ return 1; /* error, bad pad */
+
+ if (count == 0) {
+ /* do nothing */
+ } else if (count == 2) {
+ if (data_length < 1)
+ return 1; /* error, too much data */
+ n = n >> 4;
+ *data++ = n;
+ data_length--;
+ } else if (count == 3) {
+ if (data_length < 2)
+ return 1; /* error, too much data */
+ n = n >> 2;
+ *data++ = n >> 8;
+ *data++ = n;
+ data_length -= 2;
+ } else
+ return 1; /* bad encoding */
+
+ if (data_length >= *data_lenp)
+ return 1; /* error, no data */
+
+ *data_lenp = *data_lenp - data_length;
+
+ return 0; /* no error */
+}
+
+static int
+acl_text_to_data(const char *text, unsigned char *data,
+ unsigned int *data_length)
+{
+ int status;
+ unsigned int text_length;
+
+ status = acl_chk_string(text, 2 + 2 * AUTH_LARGE_BINARY_MAX_LEN + 1,
+ &text_length);
+ if (status)
+ return status;
+
+ if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
+ /* skip prefix */
+ text += 2;
+ text_length -= 2;
+ status = acl_hex_to_data(text, text_length, data, data_length);
+ } else if (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')) {
+ /* skip prefix */
+ text += 2;
+ text_length -= 2;
+ status = acl_base64_to_data(text, data, data_length);
+ } else
+ status = 1; /* prefix not recognized. */
+
+ return status;
+}
+
+static void
+acl_init_key_blk(struct auth_key_block *key_blk)
+{
+ char *str_block = key_blk->str_block;
+
+ memset(key_blk, 0, sizeof(*key_blk));
+ key_blk->str_block = str_block;
+}
+
+static void
+acl_set_key_value(struct auth_key_block *key_blk, int key_type,
+ const char *key_val)
+{
+ unsigned int length;
+ char *string;
+
+ if (key_blk->key[key_type].value_set) {
+ key_blk->dup_set = 1;
+ return;
+ }
+
+ key_blk->key[key_type].value_set = 1;
+
+ if (!key_val)
+ return;
+
+ if (acl_chk_string(key_val, AUTH_STR_MAX_LEN, &length)) {
+ key_blk->str_too_long = 1;
+ return;
+ }
+
+ length += 1;
+
+ if ((key_blk->blk_length + length) > AUTH_STR_BLOCK_MAX_LEN) {
+ key_blk->too_much_data = 1;
+ return;
+ }
+
+ string = &key_blk->str_block[key_blk->blk_length];
+
+ if (strlcpy(string, key_val, length) >= length) {
+ key_blk->too_much_data = 1;
+ return;
+ }
+ key_blk->blk_length += length;
+
+ key_blk->key[key_type].string = string;
+ key_blk->key[key_type].present = 1;
+}
+
+static const char *
+acl_get_key_val(struct auth_key_block *key_blk, int key_type)
+{
+ key_blk->key[key_type].processed = 1;
+
+ if (!key_blk->key[key_type].present)
+ return 0;
+
+ return key_blk->key[key_type].string;
+}
+
+static void
+acl_chk_key(struct iscsi_acl *client, int key_type, int *negotiated_option,
+ unsigned int option_count, int *option_list,
+ const char *(*value_to_text) (int))
+{
+ const char *key_val;
+ int length;
+ unsigned int i;
+
+ key_val = acl_get_key_val(&client->recv_key_block, key_type);
+ if (!key_val) {
+ *negotiated_option = AUTH_OPTION_NOT_PRESENT;
+ return;
+ }
+
+ while (*key_val != '\0') {
+
+ length = 0;
+
+ while (*key_val != '\0' && *key_val != ',')
+ client->scratch_key_value[length++] = *key_val++;
+
+ if (*key_val == ',')
+ key_val++;
+ client->scratch_key_value[length++] = '\0';
+
+ for (i = 0; i < option_count; i++) {
+ const char *s = (*value_to_text)(option_list[i]);
+
+ if (!s)
+ continue;
+
+ if (strcmp(client->scratch_key_value, s) == 0) {
+ *negotiated_option = option_list[i];
+ return;
+ }
+ }
+ }
+
+ *negotiated_option = AUTH_OPTION_REJECT;
+}
+
+static void
+acl_set_key(struct iscsi_acl *client, int key_type, unsigned int option_count,
+ int *option_list, const char *(*value_to_text)(int))
+{
+ unsigned int i;
+
+ if (option_count == 0) {
+ /*
+ * No valid options to send, but we always want to
+ * send something.
+ */
+ acl_set_key_value(&client->send_key_block, key_type,
+ acl_none_option_name);
+ return;
+ }
+
+ if (option_count == 1 && option_list[0] == AUTH_OPTION_NOT_PRESENT) {
+ acl_set_key_value(&client->send_key_block, key_type, 0);
+ return;
+ }
+
+ for (i = 0; i < option_count; i++) {
+ const char *s = (*value_to_text)(option_list[i]);
+
+ if (!s)
+ continue;
+
+ if (i == 0)
+ strlcpy(client->scratch_key_value, s,
+ AUTH_STR_MAX_LEN);
+ else {
+ strlcat(client->scratch_key_value, ",",
+ AUTH_STR_MAX_LEN);
+ strlcat(client->scratch_key_value, s,
+ AUTH_STR_MAX_LEN);
+ }
+ }
+
+ acl_set_key_value(&client->send_key_block, key_type,
+ client->scratch_key_value);
+}
+
+static void
+acl_chk_auth_method_key(struct iscsi_acl *client)
+{
+ acl_chk_key(client, AUTH_KEY_TYPE_AUTH_METHOD,
+ &client->negotiated_auth_method,
+ client->auth_method_valid_count,
+ client->auth_method_valid_list,
+ acl_authmethod_optn_to_text);
+}
+
+static void
+acl_set_auth_method_key(struct iscsi_acl *client,
+ unsigned int auth_method_count, int *auth_method_list)
+{
+ acl_set_key(client, AUTH_KEY_TYPE_AUTH_METHOD, auth_method_count,
+ auth_method_list, acl_authmethod_optn_to_text);
+}
+
+static void
+acl_chk_chap_alg_key(struct iscsi_acl *client)
+{
+ const char *key_val;
+ int length;
+ unsigned long number;
+ unsigned int i;
+
+ key_val = acl_get_key_val(&client->recv_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG);
+ if (!key_val) {
+ client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT;
+ return;
+ }
+
+ while (*key_val != '\0') {
+
+ length = 0;
+
+ while (*key_val != '\0' && *key_val != ',')
+ client->scratch_key_value[length++] = *key_val++;
+
+ if (*key_val == ',')
+ key_val++;
+ client->scratch_key_value[length++] = '\0';
+
+ if (acl_text_to_number(client->scratch_key_value, &number))
+ continue;
+
+
+ for (i = 0; i < client->chap_alg_count; i++)
+ if (number == (unsigned long)client->chap_alg_list[i])
+ {
+ client->negotiated_chap_alg = number;
+ return;
+ }
+ }
+
+ client->negotiated_chap_alg = AUTH_OPTION_REJECT;
+}
+
+static void
+acl_set_chap_alg_key(struct iscsi_acl *client, unsigned int chap_alg_count,
+ int *chap_alg_list)
+{
+ unsigned int i;
+
+ if (chap_alg_count == 0) {
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG, 0);
+ return;
+ }
+
+ if (chap_alg_count == 1 &&
+ chap_alg_list[0] == AUTH_OPTION_NOT_PRESENT) {
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG, 0);
+ return;
+ }
+
+ if (chap_alg_count == 1 && chap_alg_list[0] == AUTH_OPTION_REJECT) {
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG,
+ acl_reject_option_name);
+ return;
+ }
+
+ for (i = 0; i < chap_alg_count; i++) {
+ char s[20];
+
+ snprintf(s, sizeof(s), "%lu",(unsigned long)chap_alg_list[i]);
+
+ if (i == 0)
+ strlcpy(client->scratch_key_value, s,
+ AUTH_STR_MAX_LEN);
+ else {
+ strlcat(client->scratch_key_value, ",",
+ AUTH_STR_MAX_LEN);
+ strlcat(client->scratch_key_value, s,
+ AUTH_STR_MAX_LEN);
+ }
+ }
+
+ acl_set_key_value(&client->send_key_block, AUTH_KEY_TYPE_CHAP_ALG,
+ client->scratch_key_value);
+}
+
+static void
+acl_next_phase(struct iscsi_acl *client)
+{
+ switch (client->phase) {
+ case AUTH_PHASE_CONFIGURE:
+ client->phase = AUTH_PHASE_NEGOTIATE;
+ break;
+ case AUTH_PHASE_NEGOTIATE:
+ client->phase = AUTH_PHASE_AUTHENTICATE;
+
+ if (client->negotiated_auth_method == AUTH_OPTION_REJECT ||
+ client->negotiated_auth_method == AUTH_OPTION_NOT_PRESENT ||
+ client->negotiated_auth_method == AUTH_OPTION_NONE) {
+
+ client->local_state = AUTH_LOCAL_STATE_DONE;
+ client->rmt_state = AUTH_RMT_STATE_DONE;
+
+ if (client->auth_rmt) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ } else
+ client->rmt_auth_status = AUTH_STATUS_PASS;
+
+ switch (client->negotiated_auth_method) {
+ case AUTH_OPTION_REJECT:
+ client->dbg_status =
+ AUTH_DBG_STATUS_AUTH_METHOD_REJECT;
+ break;
+ case AUTH_OPTION_NOT_PRESENT:
+ client->dbg_status =
+ AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT;
+ break;
+ case AUTH_OPTION_NONE:
+ client->dbg_status =
+ AUTH_DBG_STATUS_AUTH_METHOD_NONE;
+ }
+
+ } else if (client->negotiated_auth_method == AUTH_METHOD_CHAP) {
+ client->local_state = AUTH_LOCAL_STATE_SEND_ALG;
+ client->rmt_state = AUTH_RMT_STATE_SEND_ALG;
+ } else {
+
+ client->local_state = AUTH_LOCAL_STATE_DONE;
+ client->rmt_state = AUTH_RMT_STATE_DONE;
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_METHOD_BAD;
+ }
+ break;
+ case AUTH_PHASE_AUTHENTICATE:
+ client->phase = AUTH_PHASE_DONE;
+ break;
+ case AUTH_PHASE_DONE:
+ case AUTH_PHASE_ERROR:
+ default:
+ client->phase = AUTH_PHASE_ERROR;
+ }
+}
+
+static void
+acl_local_auth(struct iscsi_acl *client)
+{
+ unsigned int chap_identifier;
+ unsigned char response_data[AUTH_CHAP_RSP_LEN];
+ unsigned long number;
+ int status;
+ enum auth_dbg_status dbg_status;
+ const char *chap_identifier_key_val;
+ const char *chap_challenge_key_val;
+
+ switch (client->local_state) {
+ case AUTH_LOCAL_STATE_SEND_ALG:
+ if (client->node_type == TYPE_INITIATOR) {
+ acl_set_chap_alg_key(client, client->chap_alg_count,
+ client->chap_alg_list);
+ client->local_state = AUTH_LOCAL_STATE_RECV_ALG;
+ break;
+ }
+ /* Fall through */
+ case AUTH_LOCAL_STATE_RECV_ALG:
+ acl_chk_chap_alg_key(client);
+
+ if (client->node_type == TYPE_TARGET)
+ acl_set_chap_alg_key(client, 1,
+ &client->negotiated_chap_alg);
+
+ /* Make sure only supported CHAP algorithm is used. */
+ if (client->negotiated_chap_alg == AUTH_OPTION_NOT_PRESENT) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_EXPECTED;
+ break;
+ } else if (client->negotiated_chap_alg == AUTH_OPTION_REJECT) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_REJECT;
+ break;
+ } else if (client->negotiated_chap_alg != AUTH_CHAP_ALG_MD5) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_BAD;
+ break;
+ }
+ if (client->node_type == TYPE_TARGET) {
+ client->local_state = AUTH_LOCAL_STATE_RECV_CHALLENGE;
+ break;
+ }
+ /* Fall through */
+ case AUTH_LOCAL_STATE_RECV_CHALLENGE:
+ chap_identifier_key_val = acl_get_key_val(&client->recv_key_block,
+ AUTH_KEY_TYPE_CHAP_IDENTIFIER);
+ chap_challenge_key_val = acl_get_key_val(&client->recv_key_block,
+ AUTH_KEY_TYPE_CHAP_CHALLENGE);
+ if (client->node_type == TYPE_TARGET) {
+ if (!chap_identifier_key_val &&
+ !chap_challenge_key_val) {
+ client->local_state = AUTH_LOCAL_STATE_DONE;
+ break;
+ }
+ }
+
+ if (!chap_identifier_key_val) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status =
+ AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED;
+ break;
+ }
+
+ if (!chap_challenge_key_val) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status =
+ AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED;
+ break;
+ }
+
+ status = acl_text_to_number(chap_identifier_key_val, &number);
+ if (status || (255 < number)) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD;
+ break;
+ }
+ chap_identifier = number;
+
+ if (client->recv_chap_challenge_status) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHALLENGE_BAD;
+ break;
+ }
+
+ if (client->node_type == TYPE_TARGET &&
+ client->recv_chap_challenge.length ==
+ client->send_chap_challenge.length &&
+ memcmp(client->recv_chap_challenge.large_binary,
+ client->send_chap_challenge.large_binary,
+ client->send_chap_challenge.length) == 0) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status =
+ AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED;
+ break;
+ }
+
+ dbg_status = acl_chap_compute_rsp(client, 0,
+ chap_identifier,
+ client->recv_chap_challenge.large_binary,
+ client->recv_chap_challenge.length,
+ response_data);
+
+ if (dbg_status != AUTH_DBG_STATUS_NOT_SET) {
+ client->local_state = AUTH_LOCAL_STATE_ERROR;
+ client->dbg_status = dbg_status;
+ break;
+ }
+
+ acl_data_to_text(response_data,
+ AUTH_CHAP_RSP_LEN, client->scratch_key_value,
+ AUTH_STR_MAX_LEN);
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_RSP,
+ client->scratch_key_value);
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_USERNAME,
+ client->username);
+
+ client->local_state = AUTH_LOCAL_STATE_DONE;
+ break;
+ case AUTH_LOCAL_STATE_DONE:
+ break;
+ case AUTH_LOCAL_STATE_ERROR:
+ default:
+ client->phase = AUTH_PHASE_ERROR;
+ }
+}
+
+static void
+acl_rmt_auth(struct iscsi_acl *client)
+{
+ unsigned char id_data[1];
+ unsigned char response_data[AUTH_STR_MAX_LEN];
+ unsigned int rsp_len = AUTH_STR_MAX_LEN;
+ unsigned char my_rsp_data[AUTH_CHAP_RSP_LEN];
+ int status;
+ enum auth_dbg_status dbg_status;
+ const char *chap_rsp_key_val;
+ const char *chap_username_key_val;
+
+ switch (client->rmt_state) {
+ case AUTH_RMT_STATE_SEND_ALG:
+ if (client->node_type == TYPE_INITIATOR) {
+ client->rmt_state = AUTH_RMT_STATE_SEND_CHALLENGE;
+ break;
+ }
+ /* Fall through */
+ case AUTH_RMT_STATE_SEND_CHALLENGE:
+ if (!client->auth_rmt) {
+ client->rmt_auth_status = AUTH_STATUS_PASS;
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_RMT_FALSE;
+ client->rmt_state = AUTH_RMT_STATE_DONE;
+ break;
+ }
+ get_random_bytes(id_data, 1);
+ client->send_chap_identifier = id_data[0];
+ snprintf(client->scratch_key_value, AUTH_STR_MAX_LEN, "%lu",
+ (unsigned long)client->send_chap_identifier);
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_IDENTIFIER,
+ client->scratch_key_value);
+
+ client->send_chap_challenge.length = client->chap_challenge_len;
+ get_random_bytes(client->send_chap_challenge.large_binary,
+ client->send_chap_challenge.length);
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_CHALLENGE, "");
+
+ client->rmt_state = AUTH_RMT_STATE_RECV_RSP;
+ break;
+ case AUTH_RMT_STATE_RECV_RSP:
+ chap_rsp_key_val = acl_get_key_val(&client->recv_key_block,
+ AUTH_KEY_TYPE_CHAP_RSP);
+ chap_username_key_val = acl_get_key_val(&client->recv_key_block,
+ AUTH_KEY_TYPE_CHAP_USERNAME);
+
+ if (!chap_rsp_key_val) {
+ client->rmt_state = AUTH_RMT_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_EXPECTED;
+ break;
+ }
+
+ if (!chap_username_key_val) {
+ client->rmt_state = AUTH_RMT_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED;
+ break;
+ }
+
+ status = acl_text_to_data(chap_rsp_key_val, response_data,
+ &rsp_len);
+
+ if (status) {
+ client->rmt_state = AUTH_RMT_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_BAD;
+ break;
+ }
+
+ if (rsp_len == AUTH_CHAP_RSP_LEN) {
+ dbg_status = acl_chap_compute_rsp(client, 1,
+ client->send_chap_identifier,
+ client->send_chap_challenge.large_binary,
+ client->send_chap_challenge.length,
+ my_rsp_data);
+
+ if (dbg_status == AUTH_DBG_STATUS_NOT_SET &&
+ memcmp(my_rsp_data, response_data,
+ AUTH_CHAP_RSP_LEN) == 0) {
+ client->rmt_state = AUTH_RMT_STATE_ERROR;
+ client->dbg_status = AUTH_DBG_STATUS_PASSWD_IDENTICAL;
+ break;
+ }
+ }
+
+ strlcpy(client->chap_username, chap_username_key_val,
+ AUTH_STR_MAX_LEN);
+
+ status = acl_chap_auth_request(client, client->chap_username,
+ client->send_chap_identifier,
+ client->send_chap_challenge.
+ large_binary,
+ client->send_chap_challenge.
+ length, response_data,
+ rsp_len);
+
+ client->rmt_auth_status = (enum auth_status) status;
+ client->auth_rsp_flag = 1;
+
+ if (client->auth_server_error_flag) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_SERVER_ERROR;
+ } else if (client->rmt_auth_status == AUTH_STATUS_PASS)
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_PASS;
+ else if (client->rmt_auth_status == AUTH_STATUS_FAIL)
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_FAIL;
+ else {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->dbg_status = AUTH_DBG_STATUS_AUTH_STATUS_BAD;
+ }
+ client->rmt_state = AUTH_RMT_STATE_DONE;
+
+ /* Fall through */
+ case AUTH_RMT_STATE_DONE:
+ break;
+ case AUTH_RMT_STATE_ERROR:
+ default:
+ client->phase = AUTH_PHASE_ERROR;
+ }
+}
+
+static void
+acl_hand_shake(struct iscsi_acl *client)
+{
+ if (client->phase == AUTH_PHASE_DONE)
+
+ /*
+ * Should only happen if authentication
+ * protocol error occured.
+ */
+ return;
+
+ if (client->node_type == TYPE_INITIATOR)
+
+ /*
+ * Target should only have set T bit on response if
+ * initiator set it on previous message.
+ */
+ if (client->recv_key_block.transit_bit &&
+ !client->transit_bit_sent_flag) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL;
+ return;
+ }
+
+ if (client->phase == AUTH_PHASE_NEGOTIATE) {
+ /*
+ * Should only happen if waiting for peer
+ * to send AuthMethod key or set Transit Bit.
+ */
+ if (client->node_type == TYPE_INITIATOR)
+ client->send_key_block.transit_bit = 1;
+ return;
+ }
+
+ if (client->rmt_state == AUTH_RMT_STATE_RECV_RSP ||
+ client->rmt_state == AUTH_RMT_STATE_DONE) {
+ if (client->node_type == TYPE_INITIATOR) {
+ if (client->recv_key_block.transit_bit) {
+ if (client->rmt_state !=
+ AUTH_RMT_STATE_DONE)
+ goto recv_transit_bit_err;
+ acl_next_phase(client);
+ } else
+ client->send_key_block.transit_bit = 1;
+ } else {
+ if (client->rmt_state == AUTH_RMT_STATE_DONE &&
+ client->rmt_auth_status != AUTH_STATUS_PASS)
+ /*
+ * Authentication failed, don't do T bit
+ * handshake.
+ */
+ acl_next_phase(client);
+ else {
+ /*
+ * Target can only set T bit on response if
+ * initiator set it on current message.
+ */
+ if (client->recv_key_block.transit_bit) {
+ client->send_key_block.transit_bit = 1;
+ acl_next_phase(client);
+ }
+ }
+ }
+ } else
+ if (client->node_type == TYPE_INITIATOR)
+ if (client->recv_key_block.transit_bit)
+ goto recv_transit_bit_err;
+ return;
+
+ recv_transit_bit_err:
+ /*
+ * Target set T bit on response but
+ * initiator was not done with authentication.
+ */
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status = AUTH_DBG_STATUS_T_BIT_SET_PREMATURE;
+}
+
+static int
+acl_rcv_end_status(struct iscsi_acl *client)
+{
+ int auth_status;
+ int key_type;
+
+ if (client->phase == AUTH_PHASE_ERROR)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase == AUTH_PHASE_DONE) {
+
+ /* Perform sanity check against configured parameters. */
+ if (client->auth_rmt && !client->auth_rsp_flag &&
+ client->rmt_auth_status == AUTH_STATUS_PASS) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->dbg_status = AUTH_DBG_STATUS_AUTHPASS_NOT_VALID;
+ }
+
+ auth_status = client->rmt_auth_status;
+
+ } else
+ auth_status = AUTH_STATUS_CONTINUE;
+
+ if (auth_status == AUTH_STATUS_CONTINUE ||
+ auth_status == AUTH_STATUS_PASS) {
+ if (client->send_key_block.dup_set) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE;
+ auth_status = AUTH_STATUS_FAIL;
+ } else if (client->send_key_block.str_too_long) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_SEND_STR_TOO_LONG;
+ auth_status = AUTH_STATUS_FAIL;
+ } else if (client->send_key_block.too_much_data) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA;
+ auth_status = AUTH_STATUS_FAIL;
+ } else {
+ /* Check that all incoming keys have been processed. */
+
+ for (key_type = AUTH_KEY_TYPE_FIRST;
+ key_type < AUTH_KEY_TYPE_MAX_COUNT; key_type++)
+ if (client->recv_key_block.key[key_type].present &&
+ !client->recv_key_block.key[key_type].
+ processed)
+ break;
+
+ if (key_type < AUTH_KEY_TYPE_MAX_COUNT) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT;
+ auth_status = AUTH_STATUS_FAIL;
+ }
+ }
+ }
+
+ if (auth_status != AUTH_STATUS_PASS &&
+ auth_status != AUTH_STATUS_CONTINUE) {
+ int auth_method_key_present = 0;
+ int chap_alg_key_present = 0;
+
+ /*
+ * Suppress send keys on error,
+ * except for AuthMethod and CHAP_A.
+ */
+ if (client->node_type == TYPE_TARGET) {
+ if (acl_get_key_val(&client->send_key_block,
+ AUTH_KEY_TYPE_AUTH_METHOD))
+ auth_method_key_present = 1;
+ else if (acl_get_key_val(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG))
+ chap_alg_key_present = 1;
+ }
+
+ acl_init_key_blk(&client->send_key_block);
+
+ if (client->node_type == TYPE_TARGET) {
+ if (auth_method_key_present &&
+ client->negotiated_auth_method ==
+ AUTH_OPTION_REJECT)
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_AUTH_METHOD,
+ acl_reject_option_name);
+ else if (chap_alg_key_present &&
+ client->negotiated_chap_alg ==
+ AUTH_OPTION_REJECT)
+ acl_set_key_value(&client->send_key_block,
+ AUTH_KEY_TYPE_CHAP_ALG,
+ acl_reject_option_name);
+ }
+ }
+ client->recv_in_progress_flag = 0;
+
+ return auth_status;
+}
+
+int
+acl_recv_begin(struct iscsi_acl *client)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase == AUTH_PHASE_ERROR)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase == AUTH_PHASE_DONE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (client->recv_in_progress_flag) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ client->recv_in_progress_flag = 1;
+
+ if (client->phase == AUTH_PHASE_CONFIGURE)
+ acl_next_phase(client);
+
+ client->transit_bit_sent_flag = client->send_key_block.transit_bit;
+
+ acl_init_key_blk(&client->recv_key_block);
+ acl_init_key_blk(&client->send_key_block);
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_recv_end(struct iscsi_acl *client, struct iscsi_session *session_handle)
+{
+ int next_phase_flag = 0;
+
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase == AUTH_PHASE_ERROR)
+ return AUTH_STATUS_ERROR;
+
+ if (!client->recv_in_progress_flag) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (client->recv_end_count > AUTH_RECV_END_MAX_COUNT) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status = AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT;
+ } else if (client->recv_key_block.dup_set) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status = AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE;
+ } else if (client->recv_key_block.str_too_long) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status = AUTH_DBG_STATUS_RECV_STR_TOO_LONG;
+ } else if (client->recv_key_block.too_much_data) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status = AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA;
+ }
+
+ client->recv_end_count++;
+ client->session_handle = session_handle;
+
+ switch (client->phase) {
+ case AUTH_PHASE_NEGOTIATE:
+ acl_chk_auth_method_key(client);
+ if (client->auth_method_valid_neg_role ==
+ AUTH_NEG_ROLE_RESPONDER) {
+ if (client->negotiated_auth_method ==
+ AUTH_OPTION_NOT_PRESENT) {
+ if (client->auth_rmt ||
+ !client->recv_key_block.transit_bit) {
+ /*
+ * No AuthMethod key from peer on
+ * first message, try moving the
+ * process along by sending the
+ * AuthMethod key.
+ */
+
+ client->auth_method_valid_neg_role =
+ AUTH_NEG_ROLE_ORIGINATOR;
+ acl_set_auth_method_key(client,
+ client->auth_method_valid_count,
+ client->auth_method_valid_list);
+ break;
+ }
+
+ /*
+ * Special case if peer sent no AuthMethod key,
+ * but did set Transit Bit, allowing this side
+ * to do a null authentication, and compelete
+ * the iSCSI security phase without either side
+ * sending the AuthMethod key.
+ */
+ } else
+ /* Send response to AuthMethod key. */
+ acl_set_auth_method_key(client, 1,
+ &client->negotiated_auth_method);
+
+ if (client->node_type == TYPE_INITIATOR)
+ acl_next_phase(client);
+ else
+ next_phase_flag = 1;
+ } else {
+
+ if (client->negotiated_auth_method ==
+ AUTH_OPTION_NOT_PRESENT) {
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ client->dbg_status =
+ AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED;
+ break;
+ }
+
+ acl_next_phase(client);
+ }
+ break;
+ case AUTH_PHASE_AUTHENTICATE:
+ case AUTH_PHASE_DONE:
+ break;
+ default:
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ switch (client->phase) {
+ case AUTH_PHASE_NEGOTIATE:
+ if (next_phase_flag)
+ acl_next_phase(client);
+ break;
+ case AUTH_PHASE_AUTHENTICATE:
+ /*
+ * Must call acl_local_auth()
+ * before acl_rmt_auth()
+ * to insure processing of the CHAP algorithm key,
+ * and to avoid leaving an in progress request to the
+ * authentication service.
+ */
+ acl_local_auth(client);
+
+ if (client->local_state != AUTH_LOCAL_STATE_ERROR)
+ acl_rmt_auth(client);
+
+ if (client->local_state == AUTH_LOCAL_STATE_ERROR ||
+ client->rmt_state == AUTH_RMT_STATE_ERROR) {
+
+ client->rmt_auth_status = AUTH_STATUS_FAIL;
+ client->phase = AUTH_PHASE_DONE;
+ /* client->dbg_status should already be set. */
+ }
+ break;
+ case AUTH_PHASE_DONE:
+ break;
+ default:
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ acl_hand_shake(client);
+
+ return acl_rcv_end_status(client);
+}
+
+const char *
+acl_get_key_name(int key_type)
+{
+ /*
+ * Note: The ordering of this table must match the order
+ * defined by enum auth_key_type in iscsi-auth-client.h.
+ */
+ static char *const key_names[AUTH_KEY_TYPE_MAX_COUNT] = {
+ "AuthMethod",
+ "CHAP_A",
+ "CHAP_N",
+ "CHAP_R",
+ "CHAP_I",
+ "CHAP_C"
+ };
+
+ if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST)
+ return 0;
+
+ return key_names[key_type];
+}
+
+int
+acl_get_next_key_type(int *key_type)
+{
+ if (*key_type >= AUTH_KEY_TYPE_LAST)
+ return AUTH_STATUS_ERROR;
+
+ if (*key_type < AUTH_KEY_TYPE_FIRST)
+ *key_type = AUTH_KEY_TYPE_FIRST;
+ else
+ (*key_type)++;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_recv_key_value(struct iscsi_acl *client, int key_type,
+ const char *user_key_val)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_NEGOTIATE &&
+ client->phase != AUTH_PHASE_AUTHENTICATE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) {
+ client->recv_chap_challenge.length =
+ AUTH_LARGE_BINARY_MAX_LEN;
+ client->recv_chap_challenge_status =
+ acl_text_to_data(user_key_val,
+ client->recv_chap_challenge.large_binary,
+ &client->recv_chap_challenge.length);
+ user_key_val = "";
+ }
+
+ acl_set_key_value(&client->recv_key_block, key_type, user_key_val);
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_send_key_val(struct iscsi_acl *client, int key_type, int *key_present,
+ char *user_key_val, unsigned int max_length)
+{
+ const char *key_val;
+
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE &&
+ client->phase != AUTH_PHASE_NEGOTIATE &&
+ client->phase != AUTH_PHASE_AUTHENTICATE &&
+ client->phase != AUTH_PHASE_DONE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ key_val = acl_get_key_val(&client->send_key_block, key_type);
+ if (key_val) {
+ if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) {
+ if (acl_data_to_text(client->send_chap_challenge.large_binary,
+ client->send_chap_challenge.length, user_key_val,
+ max_length)) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+ } else if (strlcpy(user_key_val, key_val, max_length) >=
+ max_length) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+ *key_present = 1;
+ } else
+ *key_present = 0;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_recv_transit_bit(struct iscsi_acl *client, int value)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_NEGOTIATE &&
+ client->phase != AUTH_PHASE_AUTHENTICATE) {
+
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (value)
+ client->recv_key_block.transit_bit = 1;
+ else
+ client->recv_key_block.transit_bit = 0;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_send_transit_bit(struct iscsi_acl *client, int *value)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE &&
+ client->phase != AUTH_PHASE_NEGOTIATE &&
+ client->phase != AUTH_PHASE_AUTHENTICATE &&
+ client->phase != AUTH_PHASE_DONE) {
+
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ *value = client->send_key_block.transit_bit;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+static int
+acl_set_option_list(struct iscsi_acl *client, unsigned int opt_count,
+ const int *opt_list, unsigned int *clnt_optn_count,
+ int *clnt_optn_list, unsigned int optn_max_count,
+ int (*chk_option)(int),
+ int (*chk_list)(unsigned int opt_count, const int *opt_list))
+{
+ unsigned int i, j;
+
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE ||
+ opt_count > optn_max_count) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ for (i = 0; i < opt_count; i++)
+ if (chk_option(opt_list[i])) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ /* Check for duplicate entries. */
+ for (i = 0; i < opt_count; i++)
+ for (j = 0; j < opt_count; j++) {
+ if (j == i)
+ continue;
+ if (opt_list[i] == opt_list[j]) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+ }
+
+ /* Check for key specific constraints. */
+ if (chk_list)
+ if (chk_list(opt_count, opt_list)) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ for (i = 0; i < opt_count; i++)
+ clnt_optn_list[i] = opt_list[i];
+
+ *clnt_optn_count = opt_count;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+static int
+acl_chk_auth_method_list(unsigned int option_count, const int *option_list)
+{
+ unsigned int i;
+
+ if (!option_list || option_count < 2)
+ return 1;
+
+ if (option_list[option_count - 1] != AUTH_OPTION_NONE)
+ return 1;
+
+ for (i = 0; i < (option_count - 1); i++)
+ if (option_list[i] != AUTH_OPTION_NONE)
+ return 0;
+
+ return 0;
+}
+
+static void
+acl_set_auth_method_valid(struct iscsi_acl *client)
+{
+ unsigned int i, j = 0;
+ int option = 0;
+
+ /*
+ * Following checks may need to be revised if
+ * authentication options other than CHAP and none
+ * are supported.
+ */
+ if (client->node_type == TYPE_INITIATOR) {
+ if (client->auth_rmt)
+ /*
+ * If initiator doing authentication,
+ * don't offer authentication option none.
+ */
+ option = 1;
+ else if (!client->passwd_present)
+ /*
+ * If initiator password not set,
+ * only offer authentication option none.
+ */
+ option = 2;
+ }
+
+ if (client->node_type == TYPE_TARGET) {
+ if (client->auth_rmt)
+ /*
+ * If target doing authentication,
+ * don't accept authentication option none.
+ */
+ option = 1;
+ else
+ /*
+ * If target not doing authentication,
+ * only accept authentication option none.
+ */
+ option = 2;
+ }
+
+ for (i = 0; i < client->auth_method_count; i++) {
+ if (option == 1) {
+ if (client->auth_method_list[i] == AUTH_OPTION_NONE)
+ continue;
+ } else if (option == 2)
+ if (client->auth_method_list[i] != AUTH_OPTION_NONE)
+ continue;
+ client->auth_method_valid_list[j++] = client->auth_method_list[i];
+ }
+
+ client->auth_method_valid_count = j;
+
+ acl_init_key_blk(&client->send_key_block);
+
+ if (client->node_type == TYPE_INITIATOR) {
+ if (client->auth_rmt) {
+ /*
+ * Initiator wants to authenticate target,
+ * always send AuthMethod key.
+ */
+ client->send_key_block.transit_bit = 0;
+ client->auth_method_valid_neg_role =
+ AUTH_NEG_ROLE_ORIGINATOR;
+ } else {
+ client->send_key_block.transit_bit = 1;
+ client->auth_method_valid_neg_role =
+ client->auth_method_neg_role;
+ }
+ } else {
+ client->send_key_block.transit_bit = 0;
+ client->auth_method_valid_neg_role = AUTH_NEG_ROLE_RESPONDER;
+ }
+
+ if (client->auth_method_valid_neg_role == AUTH_NEG_ROLE_ORIGINATOR)
+ acl_set_auth_method_key(client, client->auth_method_valid_count,
+ client->auth_method_valid_list);
+ else {
+ int value = AUTH_OPTION_NOT_PRESENT;
+ acl_set_auth_method_key(client, 1, &value);
+ }
+}
+
+static int
+acl_set_auth_method_list(struct iscsi_acl *client, unsigned int option_count,
+ const int *option_list)
+{
+ int status;
+
+ status = acl_set_option_list(client, option_count, option_list,
+ &client->auth_method_count,
+ client->auth_method_list,
+ AUTH_METHOD_MAX_COUNT,
+ acl_chk_auth_mthd_optn,
+ acl_chk_auth_method_list);
+
+ if (status != AUTH_STATUS_NO_ERROR)
+ return status;
+
+ /* Setting authMethod affects auth_method_valid. */
+ acl_set_auth_method_valid(client);
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+static int
+acl_chk_chap_alg_list(unsigned int option_count, const int *option_list)
+{
+ if (!option_list || option_count < 1)
+ return 1;
+
+ return 0;
+}
+
+static int
+acl_set_chap_alg_list(struct iscsi_acl *client, unsigned int option_count,
+ const int *option_list)
+{
+ return acl_set_option_list(client, option_count, option_list,
+ &client->chap_alg_count,
+ client->chap_alg_list,
+ AUTH_CHAP_ALG_MAX_COUNT,
+ acl_chk_chap_alg_optn,
+ acl_chk_chap_alg_list);
+}
+
+int
+acl_init(int node_type, int buf_desc_count, struct auth_buffer_desc *buff_desc)
+{
+ struct iscsi_acl *client;
+ struct auth_str_block *recv_str_blk;
+ struct auth_str_block *send_str_blk;
+ struct auth_large_binary *recv_chap_challenge;
+ struct auth_large_binary *send_chap_challenge;
+ int value_list[2];
+
+ if (buf_desc_count != 5 || !buff_desc)
+ return AUTH_STATUS_ERROR;
+
+ if (!buff_desc[0].address ||
+ buff_desc[0].length != sizeof(*client))
+ return AUTH_STATUS_ERROR;
+ client = (struct iscsi_acl *)buff_desc[0].address;
+
+ if (!buff_desc[1].address ||
+ buff_desc[1].length != sizeof(*recv_str_blk))
+ return AUTH_STATUS_ERROR;
+ recv_str_blk = (struct auth_str_block *)buff_desc[1].address;
+
+ if (!buff_desc[2].address ||
+ buff_desc[2].length != sizeof(*send_str_blk))
+ return AUTH_STATUS_ERROR;
+
+ send_str_blk = (struct auth_str_block *)buff_desc[2].address;
+
+ if (!buff_desc[3].address ||
+ buff_desc[3].length != sizeof(*recv_chap_challenge))
+ return AUTH_STATUS_ERROR;
+
+ recv_chap_challenge = (struct auth_large_binary *)
+ buff_desc[3].address;
+
+ if (!buff_desc[4].address ||
+ buff_desc[4].length != sizeof(*send_chap_challenge))
+ return AUTH_STATUS_ERROR;
+ send_chap_challenge = (struct auth_large_binary *)
+ buff_desc[4].address;
+ memset(client, 0, sizeof(*client));
+ memset(recv_str_blk, 0, sizeof(*recv_str_blk));
+ memset(send_str_blk, 0, sizeof(*send_str_blk));
+ memset(recv_chap_challenge, 0, sizeof(*recv_chap_challenge));
+ memset(send_chap_challenge, 0, sizeof(*send_chap_challenge));
+
+ client->recv_key_block.str_block = recv_str_blk->str_block;
+ client->send_key_block.str_block = send_str_blk->str_block;
+ client->recv_chap_challenge.large_binary = recv_chap_challenge->large_binary;
+ client->send_chap_challenge.large_binary = send_chap_challenge->large_binary;
+
+ if (node_type != TYPE_INITIATOR && node_type != TYPE_TARGET) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ client->signature = ACL_SIGNATURE;
+ client->node_type = (enum auth_node_type) node_type;
+ client->auth_rmt = 1;
+ client->passwd_present = 0;
+ client->chap_challenge_len = AUTH_CHAP_RSP_LEN;
+ client->ip_sec = 0;
+
+ client->phase = AUTH_PHASE_CONFIGURE;
+ client->negotiated_auth_method = AUTH_OPTION_NOT_PRESENT;
+ client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT;
+
+ if (client->node_type == TYPE_INITIATOR)
+ client->auth_method_neg_role = AUTH_NEG_ROLE_ORIGINATOR;
+ else
+ /* Initial value ignored for Target. */
+ client->auth_method_neg_role = AUTH_NEG_ROLE_RESPONDER;
+
+ value_list[0] = AUTH_METHOD_CHAP;
+ value_list[1] = AUTH_OPTION_NONE;
+
+ /*
+ * Must call after setting auth_rmt, password,
+ * and auth_method_neg_role
+ */
+ if (acl_set_auth_method_list(client, 2, value_list) !=
+ AUTH_STATUS_NO_ERROR) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ value_list[0] = AUTH_CHAP_ALG_MD5;
+
+ if (acl_set_chap_alg_list(client, 1, value_list) !=
+ AUTH_STATUS_NO_ERROR) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_finish(struct iscsi_acl *client)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ memset(client, 0, sizeof(*client));
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_set_user_name(struct iscsi_acl *client, const char *username)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE ||
+ acl_chk_string(username, AUTH_STR_MAX_LEN, 0)) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ if (strlcpy(client->username, username, AUTH_STR_MAX_LEN) >=
+ AUTH_STR_MAX_LEN) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_set_passwd(struct iscsi_acl *client, const unsigned char *passwd_data,
+ unsigned int passwd_length)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE ||
+ passwd_length > AUTH_STR_MAX_LEN) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ memcpy(client->passwd_data, passwd_data, passwd_length);
+ client->passwd_length = passwd_length;
+ client->passwd_present = 1;
+
+ /* Setting password may affect auth_method_valid. */
+ acl_set_auth_method_valid(client);
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ client->auth_rmt = auth_rmt;
+
+ /* Setting auth_rmt may affect auth_method_valid. */
+ acl_set_auth_method_valid(client);
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_set_ip_sec(struct iscsi_acl *client, int ip_sec)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_CONFIGURE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ client->ip_sec = ip_sec;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+int
+acl_get_dbg_status(struct iscsi_acl *client, int *value)
+{
+ if (!client || client->signature != ACL_SIGNATURE)
+ return AUTH_STATUS_ERROR;
+
+ if (client->phase != AUTH_PHASE_DONE) {
+ client->phase = AUTH_PHASE_ERROR;
+ return AUTH_STATUS_ERROR;
+ }
+
+ *value = client->dbg_status;
+
+ return AUTH_STATUS_NO_ERROR;
+}
+
+const char *
+acl_dbg_status_to_text(int dbg_status)
+{
+ /*
+ * Note: The ordering of this table must match the order
+ * defined by enum auth_dbg_status in iscsi-auth-client.h.
+ */
+ static char *const dbg_text[AUTH_DBG_STATUS_MAX_COUNT] = {
+ "Debug status not set",
+ "Authentication request passed",
+ "Authentication not enabled",
+ "Authentication request failed",
+ "AuthMethod bad",
+ "CHAP algorithm bad",
+ "Decrypt password failed",
+ "Local password too short with no IPSec",
+ "Unexpected error from authentication server",
+ "Authentication request status bad",
+ "Authentication pass status not valid",
+ "Same key set more than once on send",
+ "Key value too long on send",
+ "Too much data on send",
+ "AuthMethod key expected",
+ "CHAP algorithm key expected",
+ "CHAP identifier expected",
+ "CHAP challenge expected",
+ "CHAP response expected",
+ "CHAP username expected",
+ "AuthMethod key not present",
+ "AuthMethod negotiation failed",
+ "AuthMethod negotiated to none",
+ "CHAP algorithm negotiation failed",
+ "CHAP challange reflected",
+ "Local password same as remote",
+ "Local password not set",
+ "CHAP identifier bad",
+ "CHAP challenge bad",
+ "CHAP response bad",
+ "Unexpected key present",
+ "T bit set on response, but not on previous message",
+ "T bit set on response, but authenticaton not complete",
+ "Message count limit reached on receive",
+ "Same key set more than once on receive",
+ "Key value too long on receive",
+ "Too much data on receive"
+ };
+
+ if (dbg_status < 0 || dbg_status >= AUTH_DBG_STATUS_MAX_COUNT)
+ return "Unknown error";
+
+ return dbg_text[dbg_status];
+}
+
+int
+acl_data(unsigned char *out_data, unsigned int *out_length,
+ unsigned char *in_data, unsigned int in_length)
+{
+ if (*out_length < in_length)
+ return 1; /* error */
+
+ memcpy(out_data, in_data, in_length);
+ *out_length = in_length;
+
+ return 0; /* no error */
+}
+
diff --git a/usr/auth.h b/usr/auth.h
new file mode 100644
index 0000000..699eb3f
--- /dev/null
+++ b/usr/auth.h
@@ -0,0 +1,285 @@
+/*
+ * iSCSI Authorization Library
+ *
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * Originally based on:
+ * Copyright (C) 2001 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+#ifndef AUTH_CLIENT_H
+#define AUTH_CLIENT_H
+
+struct iscsi_session;
+
+enum {
+ AUTH_STR_MAX_LEN = 256,
+ AUTH_STR_BLOCK_MAX_LEN = 1024,
+ AUTH_LARGE_BINARY_MAX_LEN = 1024,
+ AUTH_RECV_END_MAX_COUNT = 10,
+ ACL_SIGNATURE = 0x5984B2E3,
+ AUTH_CHAP_RSP_LEN = 16,
+};
+
+/*
+ * Note: The ordering of these values are chosen to match
+ * the ordering of the keys as shown in the iSCSI spec.
+ * The order of table key_names in acl_get_key_name()
+ * must match the order defined by enum auth_key_type.
+ */
+enum auth_key_type {
+ AUTH_KEY_TYPE_NONE = -1,
+ AUTH_KEY_TYPE_FIRST = 0,
+ AUTH_KEY_TYPE_AUTH_METHOD = AUTH_KEY_TYPE_FIRST,
+ AUTH_KEY_TYPE_CHAP_ALG,
+ AUTH_KEY_TYPE_CHAP_USERNAME,
+ AUTH_KEY_TYPE_CHAP_RSP,
+ AUTH_KEY_TYPE_CHAP_IDENTIFIER,
+ AUTH_KEY_TYPE_CHAP_CHALLENGE,
+ AUTH_KEY_TYPE_MAX_COUNT,
+ AUTH_KEY_TYPE_LAST = AUTH_KEY_TYPE_MAX_COUNT - 1
+};
+
+enum {
+ /* Common options for all keys. */
+ AUTH_OPTION_REJECT = -2,
+ AUTH_OPTION_NOT_PRESENT = -1,
+ AUTH_OPTION_NONE = 1,
+
+ AUTH_METHOD_CHAP = 2,
+ AUTH_METHOD_MAX_COUNT = 2,
+
+ AUTH_CHAP_ALG_MD5 = 5,
+ AUTH_CHAP_ALG_MAX_COUNT = 2
+};
+
+enum auth_neg_role {
+ AUTH_NEG_ROLE_ORIGINATOR = 1,
+ AUTH_NEG_ROLE_RESPONDER = 2
+};
+
+enum auth_status {
+ AUTH_STATUS_NO_ERROR = 0,
+ AUTH_STATUS_ERROR,
+ AUTH_STATUS_PASS,
+ AUTH_STATUS_FAIL,
+ AUTH_STATUS_CONTINUE,
+};
+
+/*
+ * Note: The order of table dbg_text in acl_dbg_status_to_text()
+ * must match the ordered defined by enum auth_dbg_status.
+ */
+enum auth_dbg_status {
+ AUTH_DBG_STATUS_NOT_SET = 0,
+
+ AUTH_DBG_STATUS_AUTH_PASS,
+ AUTH_DBG_STATUS_AUTH_RMT_FALSE,
+
+ AUTH_DBG_STATUS_AUTH_FAIL,
+
+ AUTH_DBG_STATUS_AUTH_METHOD_BAD,
+ AUTH_DBG_STATUS_CHAP_ALG_BAD,
+ AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED,
+ AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC,
+ AUTH_DBG_STATUS_AUTH_SERVER_ERROR,
+ AUTH_DBG_STATUS_AUTH_STATUS_BAD,
+ AUTH_DBG_STATUS_AUTHPASS_NOT_VALID,
+ AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE,
+ AUTH_DBG_STATUS_SEND_STR_TOO_LONG,
+ AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA,
+
+ AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED,
+ AUTH_DBG_STATUS_CHAP_ALG_EXPECTED,
+ AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED,
+ AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED,
+ AUTH_DBG_STATUS_CHAP_RSP_EXPECTED,
+ AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED,
+
+ AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT,
+ AUTH_DBG_STATUS_AUTH_METHOD_REJECT,
+ AUTH_DBG_STATUS_AUTH_METHOD_NONE,
+ AUTH_DBG_STATUS_CHAP_ALG_REJECT,
+ AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED,
+ AUTH_DBG_STATUS_PASSWD_IDENTICAL,
+
+ AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET,
+
+ AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD,
+ AUTH_DBG_STATUS_CHALLENGE_BAD,
+ AUTH_DBG_STATUS_CHAP_RSP_BAD,
+ AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT,
+ AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL,
+ AUTH_DBG_STATUS_T_BIT_SET_PREMATURE,
+
+ AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT,
+ AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE,
+ AUTH_DBG_STATUS_RECV_STR_TOO_LONG,
+ AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA,
+ AUTH_DBG_STATUS_MAX_COUNT
+};
+
+enum auth_node_type {
+ TYPE_INITIATOR = 1,
+ TYPE_TARGET = 2
+};
+
+enum auth_phase {
+ AUTH_PHASE_CONFIGURE = 1,
+ AUTH_PHASE_NEGOTIATE,
+ AUTH_PHASE_AUTHENTICATE,
+ AUTH_PHASE_DONE,
+ AUTH_PHASE_ERROR
+};
+
+enum auth_local_state {
+ AUTH_LOCAL_STATE_SEND_ALG = 1,
+ AUTH_LOCAL_STATE_RECV_ALG,
+ AUTH_LOCAL_STATE_RECV_CHALLENGE,
+ AUTH_LOCAL_STATE_DONE,
+ AUTH_LOCAL_STATE_ERROR
+};
+
+enum auth_rmt_state {
+ AUTH_RMT_STATE_SEND_ALG = 1,
+ AUTH_RMT_STATE_SEND_CHALLENGE,
+ AUTH_RMT_STATE_RECV_RSP,
+ AUTH_RMT_STATE_DONE,
+ AUTH_RMT_STATE_ERROR
+};
+
+struct auth_buffer_desc {
+ unsigned int length;
+ void *address;
+};
+
+struct auth_key {
+ unsigned int present:1;
+ unsigned int processed:1;
+ unsigned int value_set:1;
+ char *string;
+};
+
+struct auth_large_binary_key {
+ unsigned int length;
+ unsigned char *large_binary;
+};
+
+struct auth_key_block {
+ unsigned int transit_bit:1;
+ unsigned int dup_set:1;
+ unsigned int str_too_long:1;
+ unsigned int too_much_data:1;
+ unsigned int blk_length:16;
+ char *str_block;
+ struct auth_key key[AUTH_KEY_TYPE_MAX_COUNT];
+};
+
+struct auth_str_block {
+ char str_block[AUTH_STR_BLOCK_MAX_LEN];
+};
+
+struct auth_large_binary {
+ unsigned char large_binary[AUTH_LARGE_BINARY_MAX_LEN];
+};
+
+struct iscsi_acl {
+ unsigned long signature;
+
+ enum auth_node_type node_type;
+ unsigned int auth_method_count;
+ int auth_method_list[AUTH_METHOD_MAX_COUNT];
+ enum auth_neg_role auth_method_neg_role;
+ unsigned int chap_alg_count;
+ int chap_alg_list[AUTH_CHAP_ALG_MAX_COUNT];
+ int auth_rmt;
+ char username[AUTH_STR_MAX_LEN];
+ int passwd_present;
+ unsigned int passwd_length;
+ unsigned char passwd_data[AUTH_STR_MAX_LEN];
+ unsigned int chap_challenge_len;
+ int ip_sec;
+
+ unsigned int auth_method_valid_count;
+ int auth_method_valid_list[AUTH_METHOD_MAX_COUNT];
+ int auth_method_valid_neg_role;
+
+ int recv_in_progress_flag;
+ int recv_end_count;
+ struct iscsi_session *session_handle; /*
+ * session_handle can only be
+ * used by acl_chap_auth_request
+ */
+ enum auth_phase phase;
+ enum auth_local_state local_state;
+ enum auth_rmt_state rmt_state;
+ enum auth_status rmt_auth_status;
+ enum auth_dbg_status dbg_status;
+ int negotiated_auth_method;
+ int negotiated_chap_alg;
+ int auth_rsp_flag;
+ int auth_server_error_flag;
+ int transit_bit_sent_flag;
+
+ unsigned int send_chap_identifier;
+ struct auth_large_binary_key send_chap_challenge;
+ char chap_username[AUTH_STR_MAX_LEN];
+
+ int recv_chap_challenge_status;
+ struct auth_large_binary_key recv_chap_challenge;
+
+ char scratch_key_value[AUTH_STR_MAX_LEN];
+
+ struct auth_key_block recv_key_block;
+ struct auth_key_block send_key_block;
+};
+
+extern int acl_init(int node_type, int buf_desc_count,
+ struct auth_buffer_desc *buff_desc);
+extern int acl_finish(struct iscsi_acl *client);
+
+extern int acl_recv_begin(struct iscsi_acl *client);
+extern int acl_recv_end(struct iscsi_acl *client,
+ struct iscsi_session *session_handle);
+extern const char *acl_get_key_name(int key_type);
+extern int acl_get_next_key_type(int *key_type);
+extern int acl_recv_key_value(struct iscsi_acl *client, int key_type,
+ const char *user_key_val);
+extern int acl_send_key_val(struct iscsi_acl *client, int key_type,
+ int *key_present, char *user_key_val,
+ unsigned int max_length);
+extern int acl_recv_transit_bit(struct iscsi_acl *client, int value);
+extern int acl_send_transit_bit(struct iscsi_acl *client, int *value);
+extern int acl_set_user_name(struct iscsi_acl *client, const char *username);
+extern int acl_set_passwd(struct iscsi_acl *client,
+ const unsigned char *pw_data, unsigned int pw_len);
+extern int acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt);
+extern int acl_set_ip_sec(struct iscsi_acl *client, int ip_sec);
+extern int acl_get_dbg_status(struct iscsi_acl *client, int *value);
+extern const char *acl_dbg_status_to_text(int dbg_status);
+extern enum auth_dbg_status acl_chap_compute_rsp(struct iscsi_acl *client,
+ int rmt_auth,
+ unsigned int id,
+ unsigned char *challenge_data,
+ unsigned int challenge_len,
+ unsigned char *response_data);
+extern int acl_chap_auth_request(struct iscsi_acl *client, char *username,
+ unsigned int id,
+ unsigned char *challenge_data,
+ unsigned int challenge_length,
+ unsigned char *response_data,
+ unsigned int rsp_length);
+extern int acl_data(unsigned char *out_data, unsigned int *out_length,
+ unsigned char *in_data, unsigned int in_length);
+#endif /* #ifndef ISCSIAUTHCLIENT_H */
diff --git a/usr/chap.c b/usr/chap.c
new file mode 100644
index 0000000..5448660
--- /dev/null
+++ b/usr/chap.c
@@ -0,0 +1,652 @@
+/*
+ * chap.c - support for (mutual) CHAP authentication.
+ * (c) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
+ * available under the terms of the GNU GPL v2.0
+ *
+ * heavily based on code from iscsid.c:
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>,
+ * licensed under the terms of the GNU GPL v2.0,
+ *
+ * and code taken from UNH iSCSI software:
+ * Copyright (C) 2001-2003 InterOperability Lab (IOL)
+ * University of New Hampshire (UNH)
+ * Durham, NH 03824
+ * licensed under the terms of the GNU GPL v2.0
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "iscsid.h"
+#include "md5.h"
+#include "sha1.h"
+
+#define HEX_FORMAT 0x01
+#define BASE64_FORMAT 0x02
+
+#define CHAP_DIGEST_ALG_MD5 5
+#define CHAP_DIGEST_ALG_SHA1 7
+
+#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
+
+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
+ 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]);
+ 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 = xmalloc(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]",
+ __FUNCTION__, __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]",
+ __FUNCTION__, __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, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest)
+{
+ struct MD5Context ctx;
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, &chap_id, 1);
+ MD5Update(&ctx, secret, secret_len);
+ MD5Update(&ctx, challenge, challenge_len);
+ MD5Final(digest, &ctx);
+}
+
+static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, 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);
+}
+
+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, "7")) {
+ 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);
+
+ /*
+ * FIXME: 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 = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
+
+ conn->auth.chap.challenge = xmalloc(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] = 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;
+ struct user *user_in;
+
+ if (conn->target)
+ user_in = conn->target->users_in;
+ else
+ user_in = iscsi_discovery_users_in;
+
+ if (!user_in) {
+ log_warning("CHAP initiator auth.: "
+ "No CHAP credentials configured");
+ retval = CHAP_TARGET_ERROR;
+ goto out;
+ }
+
+ value = text_key_find(conn, "CHAP_N");
+ if (!value) {
+ retval = CHAP_INITIATOR_ERROR;
+ goto out;
+ }
+
+ while (user_in) {
+ if (!strcmp(user_in->name, value))
+ break;
+ user_in = user_in->next;
+ }
+ if (!user_in) {
+ log_warning("CHAP initiator auth.: "
+ "No valid user/pass combination for initiator %s "
+ "found", conn->initiator);
+ retval = CHAP_INITIATOR_ERROR;
+ goto out;
+ }
+
+ value = text_key_find(conn, "CHAP_R");
+ if (!value) {
+ retval = CHAP_INITIATOR_ERROR;
+ goto out;
+ }
+ encoding_format = chap_check_encoding_format(value);
+ if (encoding_format < 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;
+ }
+
+ his_digest = xmalloc(digest_len);
+ if (!his_digest) {
+ retval = CHAP_TARGET_ERROR;
+ goto out;
+ }
+ our_digest = xmalloc(digest_len);
+ if (!our_digest) {
+ 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, user_in->password,
+ strlen(user_in->password),
+ 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, user_in->password,
+ strlen(user_in->password),
+ 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 user *user_out;
+
+ value = text_key_find(conn, "CHAP_I");
+ if (!value) {
+ /* initiator doesn't want target auth!? */
+ conn->state = STATE_SECURITY_DONE;
+ retval = 0;
+ goto out;
+ }
+ chap_id = strtol(value, &value, 10);
+
+ if (conn->target)
+ user_out = conn->target->user_out;
+ else
+ user_out = iscsi_discovery_user_out;
+
+ if (!user_out) {
+ log_warning("CHAP target auth.: "
+ "no outgoing credentials configured%s",
+ conn->target ? "." : " for discovery.");
+ //FIXME: CHAP_AUTH_ERROR?
+ retval = CHAP_TARGET_ERROR;
+ goto out;
+ }
+
+ value = text_key_find(conn, "CHAP_C");
+ if (!value) {
+ log_warning("CHAP target auth.: "
+ "got no challenge from initiator %s",
+ conn->initiator);
+ retval = CHAP_INITIATOR_ERROR;
+ goto out;
+ }
+
+ encoding_format = chap_check_encoding_format(value);
+ if (encoding_format < 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;
+
+ digest = xmalloc(digest_len);
+ if (!digest) {
+ retval = CHAP_TARGET_ERROR;
+ goto out;
+ }
+ response = xmalloc(response_len);
+ if (!response) {
+ 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)) {
+ //FIXME: 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, user_out->password,
+ strlen(user_out->password), challenge,
+ challenge_len, digest);
+ break;
+ case CHAP_DIGEST_ALG_SHA1:
+ chap_calc_digest_sha1(chap_id, user_out->password,
+ strlen(user_out->password), 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", user_out->name);
+ 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",
+ __FUNCTION__, __LINE__, conn->auth_state);
+ res = CHAP_TARGET_ERROR;
+ }
+
+ return res;
+}
diff --git a/usr/ctldev.c b/usr/ctldev.c
new file mode 100644
index 0000000..a72b0d5
--- /dev/null
+++ b/usr/ctldev.c
@@ -0,0 +1,89 @@
+/*
+ * Ioctl and SysFS control
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "iscsid.h"
+
+#define CTL_DEVICE "/dev/iscsictl"
+#define SYSFS_ROOT "/sysfs/class/iscsi"
+
+int ctldev_open(void)
+{
+ FILE *f = NULL;
+ char devname[256];
+ char buf[256];
+ int devn;
+ int ctlfd;
+
+ f = fopen("/proc/devices", "r");
+ if (!f) {
+ perror("Cannot open control path to the driver\n");
+ return -1;
+ }
+
+ devn = 0;
+ while (!feof(f)) {
+ if (!fgets(buf, sizeof (buf), f)) {
+ break;
+ }
+ if (sscanf(buf, "%d %s", &devn, devname) != 2) {
+ continue;
+ }
+ if (!strcmp(devname, "ietctl")) {
+ break;
+ }
+ devn = 0;
+ }
+
+ fclose(f);
+ if (!devn) {
+ printf("cannot find iscsictl in /proc/devices - "
+ "make sure the module is loaded\n");
+ return -1;
+ }
+
+ unlink(CTL_DEVICE);
+ if (mknod(CTL_DEVICE, (S_IFCHR | 0600), (devn << 8))) {
+ printf("cannot create %s %d\n", CTL_DEVICE, errno);
+ return -1;
+ }
+
+ ctlfd = open(CTL_DEVICE, O_RDWR);
+ if (ctlfd < 0) {
+ printf("cannot open %s %d\n", CTL_DEVICE, errno);
+ return -1;
+ }
+
+ return ctlfd;
+}
+
+void
+ctldev_close(int fd)
+{
+ close(fd);
+}
diff --git a/usr/initiator.h b/usr/initiator.h
new file mode 100644
index 0000000..41ef343
--- /dev/null
+++ b/usr/initiator.h
@@ -0,0 +1,151 @@
+/*
+ * iSCSI Initiator
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef INITIATOR_H
+#define INITIATOR_H
+
+#include <stdint.h>
+
+#include "iscsi_proto.h"
+#include "auth.h"
+
+/* daemon's session structure */
+struct iscsi_session {
+ int socket_fd;
+ int login_timeout;
+ int auth_timeout;
+ int active_timeout;
+ int idle_timeout;
+ int ping_timeout;
+ int vendor_specific_keys;
+ unsigned int irrelevant_keys_bitmap;
+ int send_async_text;
+ uint32_t itt;
+ uint32_t cmdsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t exp_statsn;
+ int immediate_data;
+ int initial_r2t;
+ int max_recv_data_segment_len; /* the value we declare */
+ int max_xmit_data_segment_len; /* the value declared by the target */
+ int first_burst_len;
+ int max_burst_len;
+ int data_pdu_in_order;
+ int data_seq_in_order;
+ int def_time2wait;
+ int def_time2retain;
+ int header_digest;
+ int data_digest;
+ int type;
+ int current_stage;
+ int next_stage;
+ int partial_response;
+ int portal_group_tag;
+ uint8_t isid[6];
+ uint16_t tsih;
+ int channel;
+ int target_id;
+ char target_name[TARGET_NAME_MAXLEN + 1];
+ char *target_alias;
+ char *initiator_name;
+ char *initiator_alias;
+ int ip_length;
+ uint8_t ip_address[16];
+ int port;
+ int tcp_window_size;
+ struct auth_str_block auth_recv_string_block;
+ struct auth_str_block auth_send_string_block;
+ struct auth_large_binary auth_recv_binary_block;
+ struct auth_large_binary auth_send_binary_block;
+ struct iscsi_acl auth_client_block;
+ struct iscsi_acl *auth_client;
+ int num_auth_buffers;
+ struct auth_buffer_desc auth_buffers[5];
+ int bidirectional_auth;
+ char username[AUTH_STR_MAX_LEN];
+ uint8_t password[AUTH_STR_MAX_LEN];
+ int password_length;
+ char username_in[AUTH_STR_MAX_LEN];
+ uint8_t password_in[AUTH_STR_MAX_LEN];
+ int password_length_in;
+};
+extern int iscsi_update_address(struct iscsi_session *session, char *address);
+
+/* login.c */
+
+#define ISCSI_SESSION_TYPE_NORMAL 0
+#define ISCSI_SESSION_TYPE_DISCOVERY 1
+
+/* not defined by iSCSI, but used in the login code to determine
+ * when to send the initial Login PDU
+ */
+#define ISCSI_INITIAL_LOGIN_STAGE -1
+
+#define ISCSI_TEXT_SEPARATOR '='
+
+enum iscsi_login_status {
+ LOGIN_OK = 0, /* library worked, but caller must check
+ * the status class and detail
+ */
+ LOGIN_IO_ERROR, /* PDU I/O failed, connection have been
+ * closed or reset
+ */
+ LOGIN_FAILED, /* misc. failure */
+ LOGIN_VERSION_MISMATCH, /* incompatible iSCSI protocol version */
+ LOGIN_NEGOTIATION_FAILED, /* didn't like a key value
+ * (or received an unknown key)
+ */
+ LOGIN_AUTHENTICATION_FAILED, /* auth code indicated failure */
+ LOGIN_WRONG_PORTAL_GROUP, /* portal group tag didn't match
+ * the one required
+ */
+ LOGIN_REDIRECTION_FAILED, /* couldn't handle the redirection
+ * requested by the target
+ */
+ LOGIN_INVALID_PDU, /* received an incorrect opcode,
+ * or bogus fields in a PDU
+ */
+};
+
+/* implemented in iscsi-login.c for use on all platforms */
+extern int iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length, char *param,
+ char *value);
+extern enum iscsi_login_status iscsi_login(struct iscsi_session *session,
+ char *buffer, uint32_t bufsize,
+ uint8_t * status_class,
+ uint8_t * status_detail);
+
+/* Digest types */
+#define ISCSI_DIGEST_NONE 0
+#define ISCSI_DIGEST_CRC32C 1
+#define ISCSI_DIGEST_CRC32C_NONE 2 /* offer both, prefer CRC32C */
+#define ISCSI_DIGEST_NONE_CRC32C 3 /* offer both, prefer None */
+
+#define IRRELEVANT_MAXCONNECTIONS 0x01
+#define IRRELEVANT_INITIALR2T 0x02
+#define IRRELEVANT_IMMEDIATEDATA 0x04
+#define IRRELEVANT_MAXBURSTLENGTH 0x08
+#define IRRELEVANT_FIRSTBURSTLENGTH 0x10
+#define IRRELEVANT_MAXOUTSTANDINGR2T 0x20
+#define IRRELEVANT_DATAPDUINORDER 0x40
+#define IRRELEVANT_DATASEQUENCEINORDER 0x80
+
+#endif /* INITIATOR_H */
diff --git a/usr/io.c b/usr/io.c
new file mode 100644
index 0000000..31986f8
--- /dev/null
+++ b/usr/io.c
@@ -0,0 +1,524 @@
+/*
+ * iSCSI I/O Library
+ *
+ * Copyright (C) 2002 Cisco Systems, Inc.
+ * maintained by linux-iscsi-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+
+#include "iscsi_proto.h"
+#include "initiator.h"
+#include "log.h"
+
+#define LOG_CONN_CLOSED(session) \
+ log_error("Connection to Discovery Address %u.%u.%u.%u closed", session->ip_address[0], session->ip_address[1], session->ip_address[2], session->ip_address[3])
+#define LOG_CONN_FAIL(session) \
+ log_error("Connection to Discovery Address %u.%u.%u.%u failed", session->ip_address[0], session->ip_address[1], session->ip_address[2], session->ip_address[3])
+
+static int timedout;
+
+static void
+sigalarm_handler(int unused)
+{
+ timedout = 1;
+}
+
+int
+iscsi_connect(struct iscsi_session *session)
+{
+ int ret, rc, sock, onearg;
+ struct sockaddr_in addr;
+ struct sigaction action;
+ struct sigaction old;
+
+ /* set a timeout, since the socket calls may take a long time to
+ * timeout on their own
+ */
+ memset(&action, 0, sizeof (struct sigaction));
+ memset(&old, 0, sizeof (struct sigaction));
+ action.sa_sigaction = NULL;
+ action.sa_flags = 0;
+ action.sa_handler = sigalarm_handler;
+ sigaction(SIGALRM, &action, &old);
+ timedout = 0;
+ alarm(session->login_timeout);
+
+ /* create a socket */
+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (sock < 0) {
+ log_error("cannot create TCP socket");
+ ret = 0;
+ goto done;
+ }
+
+ onearg = 1;
+ rc = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &onearg,
+ sizeof (onearg));
+ if (rc < 0) {
+ ret = 0;
+ log_error("cannot set TCP_NODELAY option on socket");
+ close(sock);
+ goto done;
+ }
+
+ /* optionally set the window sizes */
+ if (session->tcp_window_size) {
+ int window_size = session->tcp_window_size;
+ socklen_t arglen = sizeof (window_size);
+
+ if (setsockopt
+ (sock, SOL_SOCKET, SO_RCVBUF, (char *) &window_size,
+ sizeof (window_size)) < 0) {
+ log_warning("failed to set TCP recv window size "
+ "to %u\n", window_size);
+ } else
+ if (getsockopt
+ (sock, SOL_SOCKET, SO_RCVBUF, (char *) &window_size,
+ &arglen) >= 0) {
+ log_debug(4, "set TCP recv window size to %u, "
+ "actually got %u\n",
+ session->tcp_window_size, window_size);
+ }
+
+ window_size = session->tcp_window_size;
+ arglen = sizeof (window_size);
+
+ if (setsockopt
+ (sock, SOL_SOCKET, SO_SNDBUF, (char *) &window_size,
+ sizeof (window_size)) < 0) {
+ log_warning("failed to set TCP send window size "
+ "to %u\n", window_size);
+ } else
+ if (getsockopt
+ (sock, SOL_SOCKET, SO_SNDBUF, (char *) &window_size,
+ &arglen) >= 0) {
+ log_debug(4, "set TCP send window size to %u, "
+ "actually got %u\n",
+ session->tcp_window_size, window_size);
+ }
+ }
+
+ /*
+ * Build a TCP connection to the target
+ */
+ memset(&addr, 0, sizeof (addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(session->port);
+ memcpy(&addr.sin_addr.s_addr, session->ip_address,
+ MIN(sizeof (addr.sin_addr.s_addr), session->ip_length));
+ log_debug(1, "connecting to %s:%d", inet_ntoa(addr.sin_addr),
+ session->port);
+ rc = connect(sock, (struct sockaddr *) &addr, sizeof (addr));
+ if (timedout) {
+ log_debug(1, "socket %d connect timed out", sock);
+ ret = 0;
+ goto done;
+ } else if (rc < 0) {
+ log_error("cannot make connection to %s:%d",
+ inet_ntoa(addr.sin_addr), session->port);
+ close(sock);
+ ret = 0;
+ goto done;
+ } else if (log_level > 0) {
+ struct sockaddr_in local;
+ socklen_t len = sizeof (local);
+
+ if (getsockname(sock, (struct sockaddr *) &local, &len) >= 0) {
+ log_debug(1, "connected local port %d to %s:%d",
+ ntohs(local.sin_port),
+ inet_ntoa(addr.sin_addr), session->port);
+ }
+ }
+
+ ret = 1;
+
+ done:
+ alarm(0);
+ sigaction(SIGALRM, &old, NULL);
+ session->socket_fd = sock;
+ return ret;
+}
+
+void
+iscsi_disconnect(struct iscsi_session *session)
+{
+ if (session->socket_fd >= 0) {
+ log_debug(1, "disconnecting session %p, fd %d", session,
+ session->socket_fd);
+ close(session->socket_fd);
+ session->socket_fd = -1;
+ }
+}
+
+static void
+iscsi_log_text(struct iscsi_hdr *pdu, char *data)
+{
+ int dlength = ntoh24(pdu->dlength);
+ char *text = data;
+ char *end = text + dlength;
+
+ while (text && (text < end)) {
+ log_debug(4, "> %s", text);
+ text += strlen(text);
+ while ((text < end) && (*text == '\0'))
+ text++;
+ }
+}
+
+int
+iscsi_send_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr,
+ int hdr_digest, char *data, int data_digest, int timeout)
+{
+ int rc, ret = 0;
+ char *header = (char *) hdr;
+ char *end;
+ char pad[4];
+ struct iovec vec[3];
+ int pad_bytes;
+ int pdu_length = sizeof (*hdr) + hdr->hlength + ntoh24(hdr->dlength);
+ int remaining;
+ struct sigaction action;
+ struct sigaction old;
+
+ /* set a timeout, since the socket calls may take a long time
+ * to timeout on their own
+ */
+ memset(&action, 0, sizeof (struct sigaction));
+ memset(&old, 0, sizeof (struct sigaction));
+ action.sa_sigaction = NULL;
+ action.sa_flags = 0;
+ action.sa_handler = sigalarm_handler;
+ sigaction(SIGALRM, &action, &old);
+ timedout = 0;
+ alarm(timeout);
+
+ memset(&pad, 0, sizeof (pad));
+ memset(&vec, 0, sizeof (vec));
+
+ if (log_level > 0) {
+ switch (hdr->opcode & ISCSI_OPCODE_MASK) {
+ case ISCSI_OP_LOGIN:{
+ struct iscsi_login *login_hdr =
+ (struct iscsi_login_hdr *) hdr;
+
+ log_debug(4,
+ "sending login PDU with current stage "
+ "%d, next stage %d, transit 0x%x, isid"
+ " 0x%02x%02x%02x%02x%02x%02x",
+ ISCSI_LOGIN_CURRENT_STAGE(login_hdr->
+ flags),
+ ISCSI_LOGIN_NEXT_STAGE(login_hdr->
+ flags),
+ login_hdr->
+ flags & ISCSI_FLAG_LOGIN_TRANSIT,
+ login_hdr->isid[0], login_hdr->isid[1],
+ login_hdr->isid[2], login_hdr->isid[3],
+ login_hdr->isid[4],
+ login_hdr->isid[5]);
+ iscsi_log_text(hdr, data);
+ break;
+ }
+ case ISCSI_OP_TEXT:{
+ struct iscsi_text *text_hdr =
+ (struct iscsi_txt_hdr *) hdr;
+
+ log_debug(4,
+ "sending text pdu with itt %u, "
+ "CmdSN %u:",
+ ntohl(text_hdr->itt),
+ ntohl(text_hdr->cmdsn));
+ iscsi_log_text(hdr, data);
+ break;
+ }
+ case ISCSI_OP_NOOP_OUT:{
+ struct iscsi_nopout *nopout_hdr =
+ (struct iscsi_nop_out_hdr *) hdr;
+
+ log_debug(4,
+ "sending Nop-out pdu with itt %u, "
+ "ttt %u, CmdSN %u:",
+ ntohl(nopout_hdr->itt),
+ ntohl(nopout_hdr->ttt),
+ ntohl(nopout_hdr->cmdsn));
+ iscsi_log_text(hdr, data);
+ break;
+ }
+ default:
+ log_debug(4, "sending pdu opcode 0x%x:", hdr->opcode);
+ break;
+ }
+ }
+
+ /* send the PDU header */
+ header = (char *) hdr;
+ end = header + sizeof (*hdr) + hdr->hlength;
+
+ while (header < end) {
+ vec[0].iov_base = header;
+ vec[0].iov_len = end - header;
+
+ rc = writev(session->socket_fd, vec, 1);
+ if (timedout) {
+ log_error("socket %d write timed out",
+ session->socket_fd);
+ ret = 0;
+ goto done;
+ } else if ((rc <= 0) && (errno != EAGAIN)) {
+ LOG_CONN_FAIL(session);
+ ret = 0;
+ goto done;
+ } else if (rc > 0) {
+ log_debug(4, "wrote %d bytes of PDU header", rc);
+ header += rc;
+ }
+ }
+
+ /* send all the data and any padding */
+ if (pdu_length % PAD_WORD_LEN)
+ pad_bytes = PAD_WORD_LEN - (pdu_length % PAD_WORD_LEN);
+ else
+ pad_bytes = 0;
+
+ end = data + ntoh24(hdr->dlength);
+ remaining = ntoh24(hdr->dlength) + pad_bytes;
+
+ while (remaining > 0) {
+ vec[0].iov_base = data;
+ vec[0].iov_len = end - data;
+ vec[1].iov_base = (void *) &pad;
+ vec[1].iov_len = pad_bytes;
+
+ rc = writev(session->socket_fd, vec, 2);
+ if (timedout) {
+ log_error("socket %d write timed out",
+ session->socket_fd);
+ ret = 0;
+ goto done;
+ } else if ((rc <= 0) && (errno != EAGAIN)) {
+ LOG_CONN_FAIL(session);
+ ret = 0;
+ goto done;
+ } else if (rc > 0) {
+ log_debug(4, "wrote %d bytes of PDU data", rc);
+ remaining -= rc;
+ if (data < end) {
+ data += rc;
+ if (data > end)
+ data = end;
+ }
+ }
+ }
+
+ ret = 1;
+
+ done:
+ alarm(0);
+ sigaction(SIGALRM, &old, NULL);
+ timedout = 0;
+ return ret;
+}
+
+int
+iscsi_recv_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr,
+ int hdr_digest, char *data, int max_data_length, int data_digest,
+ int timeout)
+{
+ uint32_t h_bytes = 0;
+ uint32_t ahs_bytes = 0;
+ uint32_t d_bytes = 0;
+ uint32_t ahslength = 0;
+ uint32_t dlength = 0;
+ uint32_t pad = 0;
+ int rlen = 0;
+ int failed = 0;
+ char *header = (char *) hdr;
+ char *end = data + max_data_length;
+ struct sigaction action;
+ struct sigaction old;
+
+ /* set a timeout, since the socket calls may take a long
+ * time to timeout on their own
+ */
+ memset(data, 0, max_data_length);
+ memset(&action, 0, sizeof (struct sigaction));
+ memset(&old, 0, sizeof (struct sigaction));
+ action.sa_sigaction = NULL;
+ action.sa_flags = 0;
+ action.sa_handler = sigalarm_handler;
+ sigaction(SIGALRM, &action, &old);
+ timedout = 0;
+ alarm(timeout);
+
+ /* read a response header */
+ do {
+ rlen =
+ read(session->socket_fd, header, sizeof (*hdr) - h_bytes);
+ if (timedout) {
+ log_error("socket %d header read timed out",
+ session->socket_fd);
+ failed = 1;
+ goto done;
+ } else if (rlen == 0) {
+ LOG_CONN_CLOSED(session);
+ failed = 1;
+ goto done;
+ } else if ((rlen < 0) && (errno != EAGAIN)) {
+ LOG_CONN_FAIL(session);
+ failed = 1;
+ goto done;
+ } else if (rlen > 0) {
+ log_debug(4, "read %d bytes of PDU header", rlen);
+ header += rlen;
+ h_bytes += rlen;
+ }
+ } while (h_bytes < sizeof (*hdr));
+
+ log_debug(4, "read %d PDU header bytes, opcode 0x%x, dlength %u, "
+ "data %p, max %u", h_bytes, hdr->opcode,
+ ntoh24(hdr->dlength), data, max_data_length);
+
+ /* check for additional headers */
+ ahslength = hdr->hlength; /* already includes padding */
+ if (ahslength) {
+ log_warning("additional header segment length %u not supported",
+ ahslength);
+ failed = 1;
+ goto done;
+ }
+
+ /* read exactly what we expect, plus padding */
+ dlength = hdr->dlength[0] << 16;
+ dlength |= hdr->dlength[1] << 8;
+ dlength |= hdr->dlength[2];
+
+ /* if we only expected to receive a header, exit */
+ if (dlength == 0)
+ goto done;
+
+ if (data + dlength >= end) {
+ log_warning("buffer size %u too small for data length %u",
+ max_data_length, dlength);
+ failed = 1;
+ goto done;
+ }
+
+ /* read the rest into our buffer */
+ d_bytes = 0;
+ while (d_bytes < dlength) {
+ rlen =
+ read(session->socket_fd, data + d_bytes, dlength - d_bytes);
+ if (timedout) {
+ log_error("socket %d data read timed out",
+ session->socket_fd);
+ failed = 1;
+ goto done;
+ } else if (rlen == 0) {
+ LOG_CONN_CLOSED(session);
+ failed = 1;
+ goto done;
+ } else if ((rlen < 0 && errno != EAGAIN)) {
+ LOG_CONN_FAIL(session);
+ failed = 1;
+ goto done;
+ } else if (rlen > 0) {
+ log_debug(4, "read %d bytes of PDU data", rlen);
+ d_bytes += rlen;
+ }
+ }
+
+ /* handle PDU data padding */
+ pad = dlength % PAD_WORD_LEN;
+ if (pad) {
+ int pad_bytes = pad = PAD_WORD_LEN - pad;
+ char bytes[PAD_WORD_LEN];
+
+ while (pad_bytes > 0) {
+ rlen = read(session->socket_fd, &bytes, pad_bytes);
+ if (timedout) {
+ log_error("socket %d pad read timed out",
+ session->socket_fd);
+ failed = 1;
+ goto done;
+ } else if (rlen == 0) {
+ LOG_CONN_CLOSED(session);
+ failed = 1;
+ goto done;
+ } else if ((rlen < 0 && errno != EAGAIN)) {
+ LOG_CONN_FAIL(session);
+ failed = 1;
+ goto done;
+ } else if (rlen > 0) {
+ log_debug(4, "read %d pad bytes", rlen);
+ pad_bytes -= rlen;
+ }
+ }
+ }
+
+ if (log_level > 0) {
+ switch (hdr->opcode) {
+ case ISCSI_OP_TEXT_RSP:
+ log_debug(4,
+ "finished reading text PDU, %u hdr, %u "
+ "ah, %u data, %u pad",
+ h_bytes, ahs_bytes, d_bytes, pad);
+ iscsi_log_text(hdr, data);
+ break;
+ case ISCSI_OP_LOGIN_RSP:{
+ struct iscsi_login_rsp *login_rsp =
+ (struct iscsi_login_rsp_hdr *) hdr;
+
+ log_debug(4,
+ "finished reading login PDU, %u hdr, "
+ "%u ah, %u data, %u pad",
+ h_bytes, ahs_bytes, d_bytes, pad);
+ log_debug(4,
+ "login current stage %d, next stage "
+ "%d, transit 0x%x",
+ ISCSI_LOGIN_CURRENT_STAGE(login_rsp->
+ flags),
+ ISCSI_LOGIN_NEXT_STAGE(login_rsp->
+ flags),
+ login_rsp->
+ flags & ISCSI_FLAG_LOGIN_TRANSIT);
+ iscsi_log_text(hdr, data);
+ break;
+ }
+ case ISCSI_OP_ASYNC_EVENT:
+ /* FIXME: log the event info */
+ break;
+ default:
+ break;
+ }
+ }
+
+ done:
+ alarm(0);
+ sigaction(SIGALRM, &old, NULL);
+ if (timedout || failed) {
+ timedout = 0;
+ return 0;
+ } else {
+ return h_bytes + ahs_bytes + d_bytes;
+ }
+}
diff --git a/usr/ipc.c b/usr/ipc.c
new file mode 100644
index 0000000..3ebdf25
--- /dev/null
+++ b/usr/ipc.c
@@ -0,0 +1,116 @@
+/*
+ * iSCSI Administrator Utility Socket Interface
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * Originally based on:
+ * (C) 2004 FUJITA Tomonori <tomof@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+#include <errno.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include "iscsid.h"
+#include "iscsiadm.h"
+
+int
+ipc_listen(void)
+{
+ int fd, err;
+ struct sockaddr_un addr;
+
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0)
+ return fd;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_LOCAL;
+ memcpy((char *) &addr.sun_path + 1, ISCSIADM_NAMESPACE,
+ strlen(ISCSIADM_NAMESPACE));
+
+ if ((err = bind(fd, (struct sockaddr *) &addr, sizeof(addr))) < 0)
+ return err;
+
+ if ((err = listen(fd, 32)) < 0)
+ return err;
+
+ return fd;
+}
+
+void
+ipc_close(int fd)
+{
+}
+
+static void
+__ipc_handle(iscsiadm_req_t *req, iscsiadm_rsp_t *rsp)
+{
+}
+
+int
+ipc_handle(int accept_fd)
+{
+ struct sockaddr addr;
+ struct ucred cred;
+ int fd, err, len;
+ iscsiadm_req_t req;
+ iscsiadm_rsp_t rsp;
+
+ memset(&rsp, 0, sizeof(rsp));
+ len = sizeof(addr);
+ if ((fd = accept(accept_fd, (struct sockaddr *) &addr, &len)) < 0) {
+ if (errno == EINTR)
+ err = -EINTR;
+ else
+ err = -EIO;
+
+ goto out;
+ }
+
+ len = sizeof(cred);
+ if ((err = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+ (void *)&cred, &len)) < 0) {
+ rsp.err = -EPERM;
+ goto send;
+ }
+
+ if (cred.uid || cred.gid) {
+ rsp.err = -EPERM;
+ goto send;
+ }
+
+ err = read(fd, &req, sizeof(req));
+ if (err != sizeof(req)) {
+ if (err >= 0)
+ err = -EIO;
+ goto out;
+ }
+
+ __ipc_handle(&req, &rsp);
+
+send:
+ err = write(fd, &rsp, sizeof(rsp));
+ if (err != sizeof(rsp))
+ if (err >= 0)
+ err = -EIO;
+out:
+ if (fd > 0)
+ close(fd);
+ return err;
+}
diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c
new file mode 100644
index 0000000..0c46c79
--- /dev/null
+++ b/usr/iscsiadm.c
@@ -0,0 +1,82 @@
+/*
+ * iSCSI Administration Utility
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include "iscsid.h"
+#include "iscsiadm.h"
+
+static char program_name[] = "iscsiadm";
+
+static struct option const long_options[] =
+{
+ {"version", no_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0},
+};
+
+static void usage(int status)
+{
+ if (status != 0)
+ fprintf(stderr, "Try `%s --help' for more information.\n",
+ program_name);
+ else {
+ printf("Usage: %s [OPTION]\n", program_name);
+ printf("\
+iSCSI Administration Utility.\n\
+ -v, --version display version and exit\n\
+ -h, --help display this help and exit\n\
+");
+ }
+
+ exit(status == 0 ? 0 : -1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, longindex;
+
+ while ((ch = getopt_long(argc, argv, "v:h:",
+ long_options, &longindex)) >= 0) {
+ switch (ch) {
+ case 'v':
+ version();
+ break;
+ case 'h':
+ usage(0);
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/usr/iscsiadm.h b/usr/iscsiadm.h
new file mode 100644
index 0000000..d6b7af5
--- /dev/null
+++ b/usr/iscsiadm.h
@@ -0,0 +1,49 @@
+/*
+ * iSCSI Administration Utility
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+#ifndef ISCSIADM_H
+#define ISCSIADM_H
+
+/* ipc.c */
+#define ISCSIADM_NAMESPACE "ISCSIADM_ABSTRACT_NAMESPACE"
+#define ISCSIADM_NAME_LEN 128
+
+typedef enum iscsiadm_cmd {
+ C_SESSION_ADD,
+ C_SESSION_REMOVE,
+} iscsiadm_cmd_e;
+
+/* IPC Request */
+typedef struct iscsiadm_req {
+ iscsiadm_cmd_e command;
+
+ union {
+ /* messages */
+ } u;
+} iscsiadm_req_t;
+
+/* IPC Response */
+typedef struct iscsiadm_rsp {
+ int err;
+} iscsiadm_rsp_t;
+
+int ipc_handle(int accept_fd);
+int ipc_listen(void);
+void ipc_close(int fd);
+
+#endif /* ISCSIADM_H */
diff --git a/usr/iscsid.c b/usr/iscsid.c
new file mode 100644
index 0000000..7e15c39
--- /dev/null
+++ b/usr/iscsid.c
@@ -0,0 +1,238 @@
+/*
+ * iSCSI Initiator Daemon
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "iscsi_u.h"
+#include "iscsid.h"
+#include "iscsiadm.h"
+#include "log.h"
+
+#define CONFIG_FILE "/etc/iscsid.conf"
+#define PID_FILE "/var/run/iscsid.pid"
+#define SESSION_MAX 256
+#define POLL_MAX (SESSION_MAX + 2)
+#define POLL_CTRL 0
+#define POLL_IPC 1
+#define POLL_SESSION 2
+
+static char program_name[] = "iscsid";
+int ctrl_fd, ipc_fd;
+
+static struct option const long_options[] = {
+ {"config", required_argument, 0, 'c'},
+ {"foreground", no_argument, 0, 'f'},
+ {"debug", required_argument, 0, 'd'},
+ {"uid", required_argument, 0, 'u'},
+ {"gid", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0},
+};
+
+static void usage(int status)
+{
+ if (status != 0)
+ fprintf(stderr, "Try `%s --help' for more information.\n",
+ program_name);
+ else {
+ printf("Usage: %s [OPTION]\n", program_name);
+ printf("\
+iSCSI initiator daemon.\n\
+ -c, --config=[path] Execute in the config file ("
+CONFIG_FILE ").\n\
+ -f, --foreground make the program run in the foreground\n\
+ -d, --debug debuglevel print debugging information\n\
+ -u, --uid=uid run as uid, default is current user\n\
+ -g, --gid=gid run as gid, default is current user group\n\
+ -h, --help display this help and exit\n\
+ -v, --version display version and exit\n\
+");
+ }
+ exit(status == 0 ? 0 : -1);
+}
+
+void
+handle_iscsi_events(void)
+{
+ iscsi_uevent_t event;
+ int res;
+
+ while (1) {
+ res = read(ctrl_fd, &event, sizeof(event));
+ if (res < 0) {
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ continue;
+ log_error("read ctrl_fd (%d)", errno);
+ exit(1);
+ }
+
+ log_debug(1, "event cid %u sid %d state %u",
+ event.cid, event.sid, event.state);
+
+ switch (event.state) {
+ default:
+ printf("%s(%d) %u\n", __FUNCTION__, __LINE__,
+ event.state);
+ exit(-1);
+ break;
+ }
+ }
+}
+
+void
+event_loop(void)
+{
+ int res, i, opt;
+ struct pollfd *pollfd;
+
+ poll_array[POLL_CTRL].fd = ctrl_fd;
+ poll_array[POLL_CTRL].events = POLLIN;
+ poll_array[POLL_IPC].fd = ipc_fd;
+ poll_array[POLL_IPC].events = POLLIN;
+
+ for (i = 0; i < SESSION_MAX; i++) {
+ poll_array[POLL_SESSION + i].fd = -1;
+ poll_array[POLL_SESSION + i].events = 0;
+ }
+
+ while (1) {
+ res = poll(poll_array, POLL_MAX, -1);
+ if (res <= 0) {
+ if (res < 0 && errno != EINTR) {
+ perror("poll()");
+ exit(1);
+ }
+ continue;
+ }
+
+ if (poll_array[POLL_CTRL].revents)
+ handle_iscsi_events();
+
+ if (poll_array[POLL_IPC].revents)
+ iscsiadm_req_handle(ipc_fd);
+
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, longindex;
+ char *config = CONFIG_FILE;
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ while ((ch = getopt_long(argc, argv, "c:fd:u:g:vh", long_options,
+ &longindex)) >= 0) {
+ switch (ch) {
+ case 'c':
+ config = optarg;
+ break;
+ case 'f':
+ log_daemon = 0;
+ break;
+ case 'd':
+ log_level = atoi(optarg);
+ break;
+ case 'u':
+ uid = strtoul(optarg, NULL, 10);
+ break;
+ case 'g':
+ gid = strtoul(optarg, NULL, 10);
+ break;
+ case 'v':
+ version();
+ break;
+ case 'h':
+ usage(0);
+ break;
+ default:
+ usage(1);
+ break;
+ }
+ }
+
+ if ((ctrl_fd = ctldev_open()) < 0) {
+ perror("ctrl_fd\n");
+ exit(-1);
+ }
+
+ if ((ipc_fd = ipc_listen()) < 0) {
+ perror("ipc\n");
+ exit(-1);
+ }
+
+ log_init();
+ if (log_daemon) {
+ char buf[64];
+ pid_t pid;
+ int fd;
+
+ fd = open(PID_FILE, O_WRONLY|O_CREAT, 0644);
+ if (fd < 0) {
+ log_error("unable to create pid file");
+ exit(1);
+ }
+ pid = fork();
+ if (pid < 0) {
+ log_error("starting daemon failed");
+ exit(1);
+ } else if (pid)
+ exit(0);
+
+ chdir("/");
+ if (lockf(fd, F_TLOCK, 0) < 0) {
+ log_error("unable to lock pid file");
+ exit(1);
+ }
+ ftruncate(fd, 0);
+ sprintf(buf, "%d\n", getpid());
+ write(fd, buf, strlen(buf));
+
+ close(0);
+ open("/dev/null", O_RDWR);
+ dup2(0, 1);
+ dup2(0, 2);
+ setsid();
+ }
+
+ //config_scan(config);
+
+ if (uid && setuid(uid) < 0)
+ perror("setuid\n");
+
+ if (gid && setgid(gid) < 0)
+ perror("setgid\n");
+
+ /*
+ * Start Main Event Loop
+ */
+ event_loop();
+
+ return 0;
+}
diff --git a/usr/iscsid.h b/usr/iscsid.h
new file mode 100644
index 0000000..3af55b3
--- /dev/null
+++ b/usr/iscsid.h
@@ -0,0 +1,50 @@
+/*
+ * iSCSI Initiator Daemon
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef ISCSID_H
+#define ISCSID_H
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include "types.h"
+#include "iscsi_proto.h"
+
+#define BHS_SIZE 48
+
+typedef struct iscsi_pdu {
+ iscsi_hdr_t bhs;
+ void *ahs;
+ unsigned int ahssize;
+ void *data;
+ unsigned int datasize;
+} iscsi_pdu_t;
+
+/* ctldev.c */
+extern int ctrl_fd;
+
+extern int ctldev_open(void);
+
+#define version() \
+do { \
+ printf("%s version %s\n", program_name, ISCSI_VERSION_STR); \
+ exit(0); \
+} while (0)
+
+#endif /* ISCSID_H */
diff --git a/usr/log.c b/usr/log.c
new file mode 100644
index 0000000..d9e2090
--- /dev/null
+++ b/usr/log.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sys/time.h>
+
+#include "log.h"
+
+int log_daemon = 1;
+int log_level = 0;
+
+void log_init(void)
+{
+ if (log_daemon)
+ openlog("iscsid", 0, LOG_DAEMON);
+}
+
+static void dolog(int prio, const char *fmt, va_list ap)
+{
+ if (log_daemon)
+ vsyslog(prio, fmt, ap);
+ else {
+ struct timeval time;
+
+ gettimeofday(&time, NULL);
+ fprintf(stderr, "%ld.%06ld: ", time.tv_sec, time.tv_usec);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+}
+
+void log_warning(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ dolog(LOG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+void log_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ dolog(LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+void log_debug(int level, const char *fmt, ...)
+{
+ if (log_level > level) {
+ va_list ap;
+ va_start(ap, fmt);
+ dolog(LOG_DEBUG, fmt, ap);
+ va_end(ap);
+ }
+}
+
+static void __dump_line(int level, unsigned char *buf, int *cp)
+{
+ char line[16*3+5], *lp = line;
+ int i, cnt;
+
+ cnt = *cp;
+ if (!cnt)
+ return;
+ for (i = 0; i < 16; i++) {
+ if (i < cnt)
+ lp += sprintf(lp, " %02x", buf[i]);
+ else
+ lp += sprintf(lp, " ");
+ if ((i % 4) == 3)
+ lp += sprintf(lp, " |");
+ if (i >= cnt || !isprint(buf[i]))
+ buf[i] = ' ';
+ }
+ log_debug(level, "%s %.16s |", line, buf);
+ *cp = 0;
+}
+
+static void __dump_char(int level, unsigned char *buf, int *cp, int ch)
+{
+ int cnt = (*cp)++;
+
+ buf[cnt] = ch;
+ if (cnt == 15)
+ __dump_line(level, buf, cp);
+}
+
+#define dump_line() __dump_line(level, char_buf, &char_cnt)
+#define dump_char(ch) __dump_char(level, char_buf, &char_cnt, ch)
+
+void log_pdu(int level, iscsi_pdu_t *pdu)
+{
+ unsigned char char_buf[16];
+ int char_cnt = 0;
+ unsigned char *buf;
+ int i;
+ return;
+
+ if (log_level <= level)
+ return;
+
+ buf = (void *)&pdu->bhs;
+ log_debug(level, "BHS: (%p)", buf);
+ for (i = 0; i < BHS_SIZE; i++)
+ dump_char(*buf++);
+ dump_line();
+
+ buf = (void *)pdu->ahs;
+ log_debug(level, "AHS: (%p)", buf);
+ for (i = 0; i < pdu->ahssize; i++)
+ dump_char(*buf++);
+ dump_line();
+
+ buf = (void *)pdu->data;
+ log_debug(level, "Data: (%p)", buf);
+ for (i = 0; i < pdu->datasize; i++)
+ dump_char(*buf++);
+ dump_line();
+}
diff --git a/usr/log.h b/usr/log.h
new file mode 100644
index 0000000..56fe55b
--- /dev/null
+++ b/usr/log.h
@@ -0,0 +1,41 @@
+/*
+ * iSCSI Logging and Tracing Library
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * heavily based on code from log.c:
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>,
+ * licensed under the terms of the GNU GPL v2.0,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include "iscsid.h"
+
+extern int log_daemon;
+extern int log_level;
+
+extern void log_init(void);
+extern void log_warning(const char *fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+extern void log_error(const char *fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+extern void log_debug(int level, const char *fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+extern void log_pdu(int level, iscsi_pdu_t *pdu);
+
+#endif /* LOG_H */
diff --git a/usr/login.c b/usr/login.c
new file mode 100644
index 0000000..dc3665a
--- /dev/null
+++ b/usr/login.c
@@ -0,0 +1,1444 @@
+/*
+ * iSCSI Login Library
+ *
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by open-iscsi@@googlegroups.com
+ *
+ * heavily based on code from iscsi-login.c:
+ * Copyright (C) 2001 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ *
+ * Formation of iSCSI login pdu, processing the login response and other
+ * functions are defined here
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include "initiator.h"
+#include "log.h"
+
+/* caller is assumed to be well-behaved and passing NUL terminated strings */
+int
+iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu, char *data,
+ int max_data_length, char *param, char *value)
+{
+ int param_len = strlen(param);
+ int value_len = strlen(value);
+ int length = param_len + 1 + value_len + 1; /* param, separator,
+ * value, and trailing
+ * NULL
+ */
+ int pdu_length = ntoh24(pdu->dlength);
+ char *text = data;
+ char *end = data + max_data_length;
+ char *pdu_text;
+
+ /* find the end of the current text */
+ text += pdu_length;
+ pdu_text = text;
+ pdu_length += length;
+
+ if (text + length >= end) {
+ log_warning("Failed to add login text "
+ "'%s=%s'\n", param, value);
+ return 0;
+ }
+
+ /* param */
+ strncpy(text, param, param_len);
+ text += param_len;
+
+ /* separator */
+ *text++ = ISCSI_TEXT_SEPARATOR;
+
+ /* value */
+ strncpy(text, value, value_len);
+ text += value_len;
+
+ /* NUL */
+ *text++ = '\0';
+
+ /* update the length in the PDU header */
+ hton24(pdu->dlength, pdu_length);
+
+ return 1;
+}
+
+static int
+iscsi_find_key_value(char *param, char *pdu, char *pdu_end, char **value_start,
+ char **value_end)
+{
+ char *str = param;
+ char *text = pdu;
+ char *value;
+
+ if (value_start)
+ *value_start = NULL;
+ if (value_end)
+ *value_end = NULL;
+
+ /* make sure they contain the same bytes */
+ while (*str) {
+ if (text >= pdu_end)
+ return 0;
+ if (*text == '\0')
+ return 0;
+ if (*str != *text)
+ return 0;
+ str++;
+ text++;
+ }
+
+ if ((text >= pdu_end) || (*text == '\0')
+ || (*text != ISCSI_TEXT_SEPARATOR)) {
+ return 0;
+ }
+
+ /* find the value */
+ value = text + 1;
+
+ /* find the end of the value */
+ while ((text < pdu_end) && (*text))
+ text++;
+
+ if (value_start)
+ *value_start = value;
+ if (value_end)
+ *value_end = text;
+
+ return 1;
+}
+
+static enum iscsi_login_status
+get_auth_key_type(struct iscsi_acl *auth_client, char **data, char *end)
+{
+ char *key;
+ char *value = NULL;
+ char *value_end = NULL;
+ char *text = *data;
+
+ int keytype = AUTH_KEY_TYPE_NONE;
+
+ while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR) {
+ key = (char *)acl_get_key_name(keytype);
+
+ if (key && iscsi_find_key_value(key, text, end, &value,
+ &value_end)) {
+ if (acl_recv_key_value(auth_client, keytype, value) !=
+ AUTH_STATUS_NO_ERROR) {
+ log_error("login negotiation failed, can't "
+ "accept %s in security stage\n",
+ text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ text = value_end;
+ *data = text;
+ return LOGIN_OK;
+ }
+ }
+ log_error("Login negotiation failed, can't accept %s in security "
+ "stage\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+}
+
+/*
+ * try to reset the session's IP address and port, based on the TargetAddress
+ * provided
+ */
+int
+iscsi_update_address(struct iscsi_session *session, char *address)
+{
+ char *port;
+ char *tag;
+ struct hostent *hostn = NULL;
+
+ if ((tag = strrchr(address, ','))) {
+ *tag = '\0';
+ tag++;
+ }
+ if ((port = strrchr(address, ':'))) {
+ *port = '\0';
+ port++;
+ }
+
+ hostn = gethostbyname(address);
+ if (hostn == NULL) {
+ log_error("cannot resolve host name %s", address);
+ return 0;
+ }
+
+ memcpy(session->ip_address, hostn->h_addr,
+ MIN(sizeof (session->ip_address), hostn->h_length));
+ session->ip_length = hostn->h_length;
+
+ if (port) {
+ session->port = atoi(port);
+ } else {
+ session->port = ISCSI_LISTEN_PORT;
+ }
+
+ return 1;
+}
+
+static enum iscsi_login_status
+get_security_text_keys(struct iscsi_session *session, char **data,
+ struct iscsi_acl *auth_client, char *end)
+{
+ char *text = *data;
+ char *value = NULL;
+ char *value_end = NULL;
+ size_t size;
+ int tag;
+ enum iscsi_login_status ret;
+
+ /*
+ * a few keys are possible in Security stage
+ * which the auth code doesn't care about, but
+ * which we might want to see, or at least not
+ * choke on.
+ */
+ if (iscsi_find_key_value("TargetAlias", text, end, &value,
+ &value_end)) {
+ size = value_end - value;
+ session->target_alias = malloc(size + 1);
+ if (!session->target_alias) {
+ /* Alias not critical. So just print an error */
+ log_error("Login failed to allocate alias\n");
+ *data = value_end;
+ return LOGIN_OK;
+ }
+ memcpy(session->target_alias, value, size);
+ session->target_alias[size] = '\0';
+ text = value_end;
+ } else if (iscsi_find_key_value("TargetAddress", text, end, &value,
+ &value_end)) {
+ /*
+ * if possible, change the session's
+ * ip_address and port to the new
+ * TargetAddress
+ */
+ if (iscsi_update_address(session, value)) {
+ text = value_end;
+ } else {
+ log_error("Login redirection failed, "
+ "can't handle redirection to %s\n", value);
+ return LOGIN_REDIRECTION_FAILED;
+ }
+ } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end,
+ &value, &value_end)) {
+ /*
+ * We should have already obtained this
+ * via discovery.
+ * We've already picked an isid, so the
+ * most we can do is confirm we reached
+ * the portal group we were expecting to
+ */
+ tag = strtoul(value, NULL, 0);
+ if (session->portal_group_tag >= 0) {
+ if (tag != session->portal_group_tag) {
+ log_error("Portal group tag "
+ "mismatch, expected %u, "
+ "received %u\n",
+ session->portal_group_tag, tag);
+ return LOGIN_WRONG_PORTAL_GROUP;
+ }
+ } else
+ /* we now know the tag */
+ session->portal_group_tag = tag;
+
+ text = value_end;
+ } else {
+ /*
+ * any key we don't recognize either
+ * goes to the auth code, or we choke
+ * on it
+ */
+ ret = get_auth_key_type(auth_client, &text, end);
+ if (ret != LOGIN_OK)
+ return ret;
+ }
+ *data = text;
+ return LOGIN_OK;
+}
+
+static enum iscsi_login_status
+get_op_params_text_keys(struct iscsi_session *session, char **data, char *end)
+{
+ char *text = *data;
+ char *value = NULL;
+ char *value_end = NULL;
+ size_t size;
+
+ if (iscsi_find_key_value("TargetAlias", text, end, &value,
+ &value_end)) {
+ size = value_end - value;
+ if (session->target_alias &&
+ strlen(session->target_alias) == size &&
+ memcmp(session->target_alias, value, size) == 0) {
+ *data = value_end;
+ return LOGIN_OK;
+ }
+ free(session->target_alias);
+ session->target_alias = malloc(size + 1);
+ if (!session->target_alias) {
+ /* Alias not critical. So just print an error */
+ log_error("Login failed to allocate alias\n");
+ *data = value_end;
+ return LOGIN_OK;
+ }
+ memcpy(session->target_alias, value, size);
+ session->target_alias[size] = '\0';
+ text = value_end;
+ } else if (iscsi_find_key_value("TargetAddress", text, end, &value,
+ &value_end)) {
+ if (iscsi_update_address(session, value))
+ text = value_end;
+ else {
+ log_error("Login redirection failed, "
+ "can't handle redirection to %s\n",
+ value);
+ return LOGIN_REDIRECTION_FAILED;
+ }
+ } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end,
+ &value, &value_end)) {
+ /*
+ * confirm we reached the portal group we were expecting to
+ */
+ int tag = strtoul(value, NULL, 0);
+ if (session->portal_group_tag >= 0) {
+ if (tag != session->portal_group_tag) {
+ log_error("Portal group tag mismatch, "
+ "expected %u, received %u\n",
+ session->portal_group_tag, tag);
+ return LOGIN_WRONG_PORTAL_GROUP;
+ }
+ } else
+ /* we now know the tag */
+ session->portal_group_tag = tag;
+
+ text = value_end;
+ } else if (iscsi_find_key_value("InitialR2T", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL) {
+ if (value && (strcmp(value, "Yes") == 0))
+ session->initial_r2t = 1;
+ else
+ session->initial_r2t = 0;
+ } else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_INITIALR2T;
+ text = value_end;
+ } else if (iscsi_find_key_value("ImmediateData", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL) {
+ if (value && (strcmp(value, "Yes") == 0))
+ session->immediate_data = 1;
+ else
+ session->immediate_data = 0;
+ } else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_IMMEDIATEDATA;
+ text = value_end;
+ } else if (iscsi_find_key_value("MaxRecvDataSegmentLength", text, end,
+ &value, &value_end)) {
+ session->max_xmit_data_segment_len =
+ strtoul(value, NULL, 0);
+ text = value_end;
+ } else if (iscsi_find_key_value("FirstBurstLength", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL)
+ session->first_burst_len =
+ strtoul(value, NULL, 0);
+ else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_FIRSTBURSTLENGTH;
+ text = value_end;
+ } else if (iscsi_find_key_value("MaxBurstLength", text, end, &value,
+ &value_end)) {
+ /*
+ * we don't really care, since it's a limit on the target's
+ * R2Ts, but record it anwyay
+ */
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL)
+ session->max_burst_len = strtoul(value, NULL, 0);
+ else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_MAXBURSTLENGTH;
+ text = value_end;
+ } else if (iscsi_find_key_value("HeaderDigest", text, end, &value,
+ &value_end)) {
+ if (strcmp(value, "None") == 0) {
+ if (session->header_digest != ISCSI_DIGEST_CRC32C)
+ session->header_digest = ISCSI_DIGEST_NONE;
+ else {
+ log_error("Login negotiation "
+ "failed, HeaderDigest=CRC32C "
+ "is required, can't accept "
+ "%s\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else if (strcmp(value, "CRC32C") == 0) {
+ if (session->header_digest != ISCSI_DIGEST_NONE)
+ session->header_digest = ISCSI_DIGEST_CRC32C;
+ else {
+ log_error("Login negotiation "
+ "failed, HeaderDigest=None is "
+ "required, can't accept %s\n",
+ text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else {
+ log_error("Login negotiation failed, "
+ "can't accept %s\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ text = value_end;
+ } else if (iscsi_find_key_value("DataDigest", text, end, &value,
+ &value_end)) {
+ if (strcmp(value, "None") == 0) {
+ if (session->data_digest != ISCSI_DIGEST_CRC32C)
+ session->data_digest = ISCSI_DIGEST_NONE;
+ else {
+ log_error("Login negotiation "
+ "failed, DataDigest=CRC32C "
+ "is required, can't accept "
+ "%s\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else if (strcmp(value, "CRC32C") == 0) {
+ if (session->data_digest != ISCSI_DIGEST_NONE)
+ session->data_digest = ISCSI_DIGEST_CRC32C;
+ else {
+ log_error("Login negotiation "
+ "failed, DataDigest=None is "
+ "required, can't accept %s\n",
+ text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else {
+ log_error("Login negotiation failed, "
+ "can't accept %s\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ text = value_end;
+ } else if (iscsi_find_key_value("DefaultTime2Wait", text, end, &value,
+ &value_end)) {
+ session->def_time2wait = strtoul(value, NULL, 0);
+ text = value_end;
+ } else if (iscsi_find_key_value("DefaultTime2Retain", text, end,
+ &value, &value_end)) {
+ session->def_time2retain = strtoul(value, NULL, 0);
+ text = value_end;
+ } else if (iscsi_find_key_value("OFMarker", text, end, &value,
+ &value_end))
+ /* result function is AND, target must honor our No */
+ text = value_end;
+ else if (iscsi_find_key_value("OFMarkInt", text, end, &value,
+ &value_end))
+ /* we don't do markers, so we don't care */
+ text = value_end;
+ else if (iscsi_find_key_value("IFMarker", text, end, &value,
+ &value_end))
+ /* result function is AND, target must honor our No */
+ text = value_end;
+ else if (iscsi_find_key_value("IFMarkInt", text, end, &value,
+ &value_end))
+ /* we don't do markers, so we don't care */
+ text = value_end;
+ else if (iscsi_find_key_value("DataPDUInOrder", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL) {
+ if (value && strcmp(value, "Yes") == 0)
+ session->data_pdu_in_order = 1;
+ else
+ session->data_pdu_in_order = 0;
+ } else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_DATAPDUINORDER;
+ text = value_end;
+ } else if (iscsi_find_key_value ("DataSequenceInOrder", text, end,
+ &value, &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL)
+ if (value && strcmp(value, "Yes") == 0)
+ session->data_seq_in_order = 1;
+ else
+ session->data_seq_in_order = 0;
+ else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_DATASEQUENCEINORDER;
+ text = value_end;
+ } else if (iscsi_find_key_value("MaxOutstandingR2T", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL) {
+ if (strcmp(value, "1")) {
+ log_error("Login negotiation "
+ "failed, can't accept Max"
+ "OutstandingR2T %s\n", value);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_MAXOUTSTANDINGR2T;
+ text = value_end;
+ } else if (iscsi_find_key_value("MaxConnections", text, end, &value,
+ &value_end)) {
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL) {
+ if (strcmp(value, "1")) {
+ log_error("Login negotiation "
+ "failed, can't accept Max"
+ "Connections %s\n", value);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ } else
+ session->irrelevant_keys_bitmap |=
+ IRRELEVANT_MAXCONNECTIONS;
+ text = value_end;
+ } else if (iscsi_find_key_value("ErrorRecoveryLevel", text, end,
+ &value, &value_end)) {
+ if (strcmp(value, "0")) {
+ log_error("Login negotiation failed, "
+ "can't accept ErrorRecovery %s\n",
+ value);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ text = value_end;
+ } else if (iscsi_find_key_value ("X-com.cisco.protocol", text, end,
+ &value, &value_end)) {
+ if (strcmp(value, "NotUnderstood") &&
+ strcmp(value, "Reject") &&
+ strcmp(value, "Irrelevant") &&
+ strcmp(value, "draft20")) {
+ /* if we didn't get a compatible protocol, fail */
+ log_error("Login version mismatch, "
+ "can't accept protocol %s\n", value);
+ return LOGIN_VERSION_MISMATCH;
+ }
+ text = value_end;
+ } else if (iscsi_find_key_value("X-com.cisco.PingTimeout", text, end,
+ &value, &value_end))
+ /* we don't really care what the target ends up using */
+ text = value_end;
+ else if (iscsi_find_key_value("X-com.cisco.sendAsyncText", text, end,
+ &value, &value_end))
+ /* we don't bother for the target response */
+ text = value_end;
+ else {
+ log_error("Login negotiation failed, couldn't "
+ "recognize text %s\n", text);
+ return LOGIN_NEGOTIATION_FAILED;
+ }
+ *data = text;
+ return LOGIN_OK;
+}
+
+static enum iscsi_login_status
+check_security_stage_status(struct iscsi_session *session,
+ struct iscsi_acl *auth_client)
+{
+ int debug_status = 0;
+
+ switch (acl_recv_end(auth_client, session)) {
+ case AUTH_STATUS_CONTINUE:
+ /* continue sending PDUs */
+ break;
+
+ case AUTH_STATUS_PASS:
+ break;
+
+ case AUTH_STATUS_NO_ERROR: /* treat this as an error,
+ * since we should get a
+ * different code
+ */
+ case AUTH_STATUS_ERROR:
+ case AUTH_STATUS_FAIL:
+ default:
+ if (acl_get_dbg_status(auth_client, &debug_status) !=
+ AUTH_STATUS_NO_ERROR)
+ log_error("Login authentication failed "
+ "with target %s, %s\n",
+ session->target_name,
+ acl_dbg_status_to_text(debug_status));
+ else
+ log_error("Login authentication failed "
+ "with target %s\n",
+ session->target_name);
+ return LOGIN_AUTHENTICATION_FAILED;
+ }
+ return LOGIN_OK;
+}
+
+/*
+ * this assumes the text data is always NULL terminated. The caller can
+ * always arrange for that by using a slightly larger buffer than the max PDU
+ * size, and then appending a NULL to the PDU.
+ */
+static enum iscsi_login_status
+iscsi_process_login_response(struct iscsi_session *session,
+ iscsi_login_rsp_t *login_rsp,
+ char *data, int max_data_length)
+{
+ int transit = login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT;
+ char *text = data;
+ char *end;
+ int pdu_current_stage, pdu_next_stage;
+ enum iscsi_login_status ret;
+ struct iscsi_acl *auth_client;
+
+ auth_client = (session->auth_buffers && session->num_auth_buffers) ?
+ (struct iscsi_acl *)session->auth_buffers[0].address : NULL;
+
+ end = text + ntoh24(login_rsp->dlength) + 1;
+ if (end >= (data + max_data_length)) {
+ log_error("Login failed, process_login_response "
+ "buffer too small to guarantee NULL "
+ "termination\n");
+ return LOGIN_FAILED;
+ }
+
+ /* guarantee a trailing NUL */
+ *end = '\0';
+
+ /* if the response status was success, sanity check the response */
+ if (login_rsp->status_class == ISCSI_STATUS_CLS_SUCCESS) {
+ /* check the active version */
+ if (login_rsp->active_version != ISCSI_DRAFT20_VERSION) {
+ log_error("Login version mismatch, "
+ "received incompatible active iSCSI "
+ "version 0x%02x, expected version "
+ "0x%02x\n",
+ login_rsp->active_version,
+ ISCSI_DRAFT20_VERSION);
+ return LOGIN_VERSION_MISMATCH;
+ }
+
+ /* make sure the current stage matches */
+ pdu_current_stage = (login_rsp->flags &
+ ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2;
+ if (pdu_current_stage != session->current_stage) {
+ log_error("Received invalid login PDU, "
+ "current stage mismatch, session %d, "
+ "response %d\n", session->current_stage,
+ pdu_current_stage);
+ return LOGIN_INVALID_PDU;
+ }
+
+ /*
+ * make sure that we're actually advancing if the T-bit is set
+ */
+ pdu_next_stage = login_rsp->flags &
+ ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK;
+ if (transit && (pdu_next_stage <= session->current_stage))
+ return LOGIN_INVALID_PDU;
+ }
+
+ if (session->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) {
+ if (acl_recv_begin(auth_client) != AUTH_STATUS_NO_ERROR) {
+ log_error("Login failed because "
+ "acl_recv_begin failed\n");
+ return LOGIN_FAILED;
+ }
+
+ if (acl_recv_transit_bit(auth_client, transit) !=
+ AUTH_STATUS_NO_ERROR) {
+ log_error("Login failed because "
+ "acl_recv_transit_bit failed\n");
+ return LOGIN_FAILED;
+ }
+ }
+
+ /* scan the text data */
+ while (text && (text < end)) {
+ /* skip any NULs separating each text key=value pair */
+ while ((text < end) && (*text == '\0'))
+ text++;
+ if (text >= end)
+ break;
+
+ /* handle keys appropriate for each stage */
+ switch (session->current_stage) {
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:{
+ ret = get_security_text_keys(session, &text,
+ auth_client, end);
+ if (ret != LOGIN_OK)
+ return ret;
+ break;
+ }
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{
+ ret = get_op_params_text_keys(session, &text,
+ end);
+ if (ret != LOGIN_OK)
+ return ret;
+ break;
+ }
+ default:
+ return LOGIN_FAILED;
+ }
+ }
+
+ if (session->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) {
+ ret = check_security_stage_status(session, auth_client);
+ if (ret != LOGIN_OK)
+ return ret;
+ }
+ /* record some of the PDU fields for later use */
+ session->tsih = ntohs(login_rsp->tsih);
+ session->exp_cmdsn = ntohl(login_rsp->exp_cmdsn);
+ session->max_cmdsn = ntohl(login_rsp->max_cmdsn);
+ if (login_rsp->status_class == ISCSI_STATUS_CLS_SUCCESS)
+ session->exp_statsn = ntohl(login_rsp->statsn) + 1;
+
+ if (transit) {
+ /* advance to the next stage */
+ session->partial_response = 0;
+ session->current_stage = login_rsp->flags &
+ ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK;
+ session->irrelevant_keys_bitmap = 0;
+ } else
+ /*
+ * we got a partial response, don't advance,
+ * more negotiation to do
+ */
+ session->partial_response = 1;
+
+ return LOGIN_OK; /* this PDU is ok, though the login process
+ * may not be done yet
+ */
+}
+
+static int
+add_params_normal_session(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length)
+{
+ char value[AUTH_STR_MAX_LEN];
+
+ /* these are only relevant for normal sessions */
+ if (!iscsi_add_text(session, pdu, data, max_data_length, "InitialR2T",
+ session->initial_r2t ? "Yes" : "No"))
+ return 0;
+
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "ImmediateData",
+ session->immediate_data ? "Yes" : "No"))
+ return 0;
+
+ sprintf(value, "%d", session->max_burst_len);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxBurstLength", value))
+ return 0;
+
+ sprintf(value, "%d",session->first_burst_len);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "FirstBurstLength", value))
+ return 0;
+
+ /* these we must have */
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxOutstandingR2T", "1"))
+ return 0;
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxConnections", "1"))
+ return 0;
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataPDUInOrder", "Yes"))
+ return 0;
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataSequenceInOrder", "Yes"))
+ return 0;
+
+ return 1;
+}
+
+static int
+add_vendor_specific_text(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length)
+{
+ char value[AUTH_STR_MAX_LEN];
+
+ /*
+ * adjust the target's PingTimeout for normal sessions,
+ * so that it matches the driver's ping timeout. The
+ * network probably has the same latency in both
+ * directions, so the values ought to match.
+ */
+ if (session->ping_timeout >= 0) {
+ sprintf(value, "%d", session->ping_timeout);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "X-com.cisco.PingTimeout", value))
+ return 0;
+ }
+
+ if (session->send_async_text >= 0)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "X-com.cisco.sendAsyncText",
+ session->send_async_text ? "Yes" : "No"))
+ return 0;
+
+ /*
+ * vendor-specific protocol specification. list of protocol level
+ * strings in order of preference allowable values are: draft<n>
+ * (e.g. draft8), rfc<n> (e.g. rfc666).
+ * For example: "X-com.cisco.protocol=draft20,draft8" requests draft 20,
+ * or 8 if 20 isn't supported. "X-com.cisco.protocol=draft8,draft20"
+ * requests draft 8, or 20 if 8 isn't supported. Targets that
+ * understand this key SHOULD return the protocol level they selected
+ * as a response to this key, though the active_version may be
+ * sufficient to distinguish which protocol was chosen.
+ * Note: This probably won't work unless we start in op param stage,
+ * since the security stage limits what keys we can send, and we'd need
+ * to have sent this on the first PDU of the login. Keep sending it for
+ * informational use, and so that we can sanity check things later if
+ * the RFC and draft20 are using the same active version number,
+ * but have non-trivial differences.
+ */
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "X-com.cisco.protocol", "draft20"))
+ return 0;
+
+ return 1;
+}
+
+static int
+check_irrelevant_keys(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length)
+{
+ /* If you receive irrelevant keys, just check them from the irrelevant
+ * keys bitmap and respond with the key=Irrelevant text
+ */
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXCONNECTIONS)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxConnections", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_INITIALR2T)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "InitialR2T", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_IMMEDIATEDATA)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "ImmediateData", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXBURSTLENGTH)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxBurstLength", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_FIRSTBURSTLENGTH)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "FirstBurstLength", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXOUTSTANDINGR2T)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxOutstandingR2T", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_DATAPDUINORDER)
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataPDUInOrder", "Irrelevant"))
+ return 0;
+
+ if (session->irrelevant_keys_bitmap & IRRELEVANT_DATASEQUENCEINORDER )
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataSequenceInOrder", "Irrelevant"))
+ return 0;
+
+ return 1;
+}
+
+static int
+fill_crc_digest_text(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length)
+{
+ switch (session->header_digest) {
+ case ISCSI_DIGEST_NONE:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "HeaderDigest", "None"))
+ return 0;
+ break;
+ case ISCSI_DIGEST_CRC32C:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "HeaderDigest", "CRC32C"))
+ return 0;
+ break;
+ case ISCSI_DIGEST_CRC32C_NONE:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "HeaderDigest", "CRC32C,None"))
+ return 0;
+ break;
+ default:
+ case ISCSI_DIGEST_NONE_CRC32C:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "HeaderDigest", "None,CRC32C"))
+ return 0;
+ break;
+ }
+
+ switch (session->data_digest) {
+ case ISCSI_DIGEST_NONE:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataDigest", "None"))
+ return 0;
+ break;
+ case ISCSI_DIGEST_CRC32C:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataDigest", "CRC32C"))
+ return 0;
+ break;
+ case ISCSI_DIGEST_CRC32C_NONE:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataDigest", "CRC32C,None"))
+ return 0;
+ break;
+ default:
+ case ISCSI_DIGEST_NONE_CRC32C:
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DataDigest", "None,CRC32C"))
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+static int
+fill_op_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ char *data, int max_data_length, int *transit)
+{
+ char value[AUTH_STR_MAX_LEN];
+
+ /* we always try to go from op params to full feature stage */
+ session->current_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
+ session->next_stage = ISCSI_FULL_FEATURE_PHASE;
+ *transit = 1;
+
+ /*
+ * If we haven't gotten a partial response, then either we shouldn't be
+ * here, or we just switched to this stage, and need to start offering
+ * keys.
+ */
+ if (!session->partial_response) {
+ /*
+ * request the desired settings the first time
+ * we are in this stage
+ */
+ if (!fill_crc_digest_text(session, pdu, data, max_data_length))
+ return 0;
+
+ sprintf(value, "%d", session->max_recv_data_segment_len);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "MaxRecvDataSegmentLength", value))
+ return 0;
+
+ sprintf(value, "%d", session->def_time2wait);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DefaultTime2Wait", value))
+ return 0;
+
+ sprintf(value, "%d", session->def_time2retain);
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "DefaultTime2Retain", value))
+ return 0;
+
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "IFMarker", "No"))
+ return 0;
+
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "OFMarker", "No"))
+ return 0;
+
+ if (!iscsi_add_text(session, pdu, data, max_data_length,
+ "ErrorRecoveryLevel", "0"))
+ return 0;
+
+ if (session->type == ISCSI_SESSION_TYPE_NORMAL)
+ if (!add_params_normal_session(session, pdu, data,
+ max_data_length))
+ return 0;
+
+ /*
+ * Note: 12.22 forbids vendor-specific keys on discovery
+ * sessions, so the caller is violating the spec if it asks for
+ * these on a discovery session.
+ */
+ if (session->vendor_specific_keys)
+ if (!add_vendor_specific_text(session, pdu, data,
+ max_data_length))
+ return 0;
+ } else if (!check_irrelevant_keys(session, pdu, data, max_data_length))
+ return 0;
+
+ return 1;
+}
+
+static void
+enum_auth_keys(struct iscsi_acl *auth_client, struct iscsi_hdr *pdu,
+ char *data, int max_data_length, int keytype)
+{
+ int present = 0, rc;
+ char *key = (char *)acl_get_key_name(keytype);
+ int key_length = key ? strlen(key) : 0;
+ int pdu_length = ntoh24(pdu->dlength);
+ char *auth_value = data + pdu_length + key_length + 1;
+ unsigned int max_length = max_data_length - (pdu_length
+ + key_length + 1);
+
+ /*
+ * add the key/value pairs the auth code wants to send
+ * directly to the PDU, since they could in theory be large.
+ */
+ rc = acl_send_key_val(auth_client, keytype, &present, auth_value,
+ max_length);
+ if ((rc == AUTH_STATUS_NO_ERROR) && present) {
+ /* actually fill in the key */
+ strncpy(&data[pdu_length], key, key_length);
+ pdu_length += key_length;
+ data[pdu_length] = '=';
+ pdu_length++;
+ /*
+ * adjust the PDU's data segment length
+ * to include the value and trailing NUL
+ */
+ pdu_length += strlen(auth_value) + 1;
+ hton24(pdu->dlength, pdu_length);
+ }
+}
+
+static int
+fill_security_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu,
+ struct iscsi_acl *auth_client, char *data,
+ int max_data_length, int *transit)
+{
+ int keytype = AUTH_KEY_TYPE_NONE;
+ int rc = acl_send_transit_bit(auth_client, transit);
+
+ /* see if we're ready for a stage change */
+ if (rc != AUTH_STATUS_NO_ERROR)
+ return 0;
+
+ if (*transit) {
+ /*
+ * discovery sessions can go right to full-feature phase,
+ * unless they want to non-standard values for the few relevant
+ * keys, or want to offer vendor-specific keys
+ */
+ if (session->type == ISCSI_SESSION_TYPE_DISCOVERY)
+ if ((session->header_digest != ISCSI_DIGEST_NONE) ||
+ (session->data_digest != ISCSI_DIGEST_NONE) ||
+ (session-> max_recv_data_segment_len !=
+ DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH) ||
+ session->vendor_specific_keys)
+ session->next_stage =
+ ISCSI_OP_PARMS_NEGOTIATION_STAGE;
+ else
+ session->next_stage = ISCSI_FULL_FEATURE_PHASE;
+ else
+ session->next_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
+ } else
+ session->next_stage = ISCSI_SECURITY_NEGOTIATION_STAGE;
+
+ /* enumerate all the keys the auth code might want to send */
+ while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR)
+ enum_auth_keys(auth_client, pdu, data, max_data_length,
+ keytype);
+
+ return 1;
+}
+
+/**
+ * iscsi_make_login_pdu - Prepare the login pdu to be sent to iSCSI target.
+ * @session: session for which login is initiated.
+ * @pdu: login header
+ * @data: contains text keys to be negotiated during login
+ * @max_data_length: data size
+ *
+ * Description:
+ * Based on whether authentication is enabled or not, corresponding text
+ * keys are filled up in login pdu.
+ *
+ **/
+static int
+iscsi_make_login_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr,
+ char *data, int max_data_length)
+{
+ int transit = 0;
+ int ret;
+ iscsi_login_t *login_hdr = (iscsi_login_t *)hdr;
+ struct iscsi_acl *auth_client;
+
+ auth_client = (session->auth_buffers && session->num_auth_buffers) ?
+ (struct iscsi_acl *)session->auth_buffers[0].address : NULL;
+
+ /* initialize the PDU header */
+ memset(login_hdr, 0, sizeof(*login_hdr));
+ login_hdr->opcode = ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE;
+ login_hdr->cid = 0;
+ memcpy(login_hdr->isid, session->isid, sizeof(session->isid));
+ login_hdr->tsih = 0;
+ login_hdr->cmdsn = htonl(session->cmdsn);
+ /* don't increment on immediate */
+ login_hdr->min_version = ISCSI_DRAFT20_VERSION;
+ login_hdr->max_version = ISCSI_DRAFT20_VERSION;
+
+ /* we have to send 0 until full-feature stage */
+ login_hdr->exp_statsn = htonl(session->exp_statsn);
+
+ /*
+ * the very first Login PDU has some additional requirements,
+ * and we need to decide what stage to start in.
+ */
+ if (session->current_stage == ISCSI_INITIAL_LOGIN_STAGE) {
+ if (session->initiator_name && session->initiator_name[0]) {
+ if (!iscsi_add_text(session, hdr, data, max_data_length,
+ "InitiatorName", session->initiator_name))
+ return 0;
+ } else {
+ log_error("InitiatorName is required "
+ "on the first Login PDU\n");
+ return 0;
+ }
+ if (session->initiator_alias && session->initiator_alias[0]) {
+ if (!iscsi_add_text(session, hdr, data, max_data_length,
+ "InitiatorAlias", session->initiator_alias))
+ return 0;
+ }
+
+ if ((session->target_name && session->target_name[0]) &&
+ (session->type == ISCSI_SESSION_TYPE_NORMAL)) {
+ if (!iscsi_add_text(session, hdr, data, max_data_length,
+ "TargetName", session->target_name))
+ return 0;
+ }
+
+ if (!iscsi_add_text(session, hdr, data, max_data_length,
+ "SessionType", (session->type ==
+ ISCSI_SESSION_TYPE_DISCOVERY) ? "Discovery" : "Normal"))
+ return 0;
+
+ if (auth_client)
+ /* we're prepared to do authentication */
+ session->current_stage = session->next_stage =
+ ISCSI_SECURITY_NEGOTIATION_STAGE;
+ else
+ /* can't do any authentication, skip that stage */
+ session->current_stage = session->next_stage =
+ ISCSI_OP_PARMS_NEGOTIATION_STAGE;
+ }
+
+ /* fill in text based on the stage */
+ switch (session->current_stage) {
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{
+ ret = fill_op_params_text(session, hdr, data,
+ max_data_length, &transit);
+ if (!ret)
+ return ret;
+ break;
+ }
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:{
+ ret = fill_security_params_text(session, hdr,
+ auth_client, data,
+ max_data_length,
+ &transit);
+ if (!ret)
+ return ret;
+ break;
+ }
+ case ISCSI_FULL_FEATURE_PHASE:
+ log_error("Can't send login PDUs in full "
+ "feature phase\n");
+ return 0;
+ default:
+ log_error("Can't send login PDUs in unknown "
+ "stage %d\n", session->current_stage);
+ return 0;
+ }
+
+ /* fill in the flags */
+ login_hdr->flags = 0;
+ login_hdr->flags |= session->current_stage << 2;
+ if (transit) {
+ /* transit to the next stage */
+ login_hdr->flags |= session->next_stage;
+ login_hdr->flags |= ISCSI_FLAG_LOGIN_TRANSIT;
+ } else
+ /* next == current */
+ login_hdr->flags |= session->current_stage;
+
+ return 1;
+}
+
+static enum iscsi_login_status
+check_for_authentication(struct iscsi_session *session,
+ struct iscsi_acl *auth_client)
+{
+ enum iscsi_login_status ret = LOGIN_FAILED;
+
+ auth_client = (struct iscsi_acl *)session->auth_buffers[0].address;
+
+ /* prepare for authentication */
+ if (acl_init(TYPE_INITIATOR, session->num_auth_buffers,
+ session->auth_buffers) != AUTH_STATUS_NO_ERROR) {
+ log_error("Couldn't initialize authentication\n");
+ return LOGIN_FAILED;
+ }
+
+ if (session->username &&
+ (acl_set_user_name(auth_client, session->username) !=
+ AUTH_STATUS_NO_ERROR)) {
+ log_error("Couldn't set username\n");
+ goto end;
+ }
+
+ if (session->password && (acl_set_passwd(auth_client,
+ session->password, session->password_length) !=
+ AUTH_STATUS_NO_ERROR)) {
+ log_error("Couldn't set password\n");
+ goto end;
+ }
+
+ if (acl_set_ip_sec(auth_client, 1) != AUTH_STATUS_NO_ERROR) {
+ log_error("Couldn't set IPSec\n");
+ goto end;
+ }
+
+ if (acl_set_auth_rmt(auth_client, session->bidirectional_auth) !=
+ AUTH_STATUS_NO_ERROR) {
+ log_error("Couldn't set remote authentication\n");
+ goto end;
+ }
+ return LOGIN_OK;
+
+ end:
+ if (auth_client && acl_finish(auth_client) != AUTH_STATUS_NO_ERROR) {
+ log_error("Login failed, error finishing "
+ "auth_client\n");
+ if (ret == LOGIN_OK)
+ ret = LOGIN_FAILED;
+ }
+ return ret;
+}
+
+static enum iscsi_login_status
+check_status_login_response(struct iscsi_session *session,
+ iscsi_login_rsp_t *login_rsp,
+ char *data, int max_data_length, int *final)
+{
+ enum iscsi_login_status ret;
+
+ switch (login_rsp->status_class) {
+ case ISCSI_STATUS_CLS_SUCCESS:
+ /* process this response and possibly continue sending PDUs */
+ ret = iscsi_process_login_response(session, login_rsp,
+ data, max_data_length);
+ if (ret != LOGIN_OK) /* pass back whatever
+ * error we discovered
+ */
+ *final = 1;
+ break;
+ case ISCSI_STATUS_CLS_REDIRECT:
+ /*
+ * we need to process this response to get the
+ * TargetAddress of the redirect, but we don't care
+ * about the return code.
+ */
+ iscsi_process_login_response(session, login_rsp,
+ data, max_data_length);
+ ret = LOGIN_OK;
+ *final = 1;
+ case ISCSI_STATUS_CLS_INITIATOR_ERR:
+ if (login_rsp->status_detail ==
+ ISCSI_LOGIN_STATUS_AUTH_FAILED) {
+ log_error("Login failed to authenticate "
+ "with target %s\n",
+ session->target_name);
+ }
+ ret = LOGIN_OK;
+ *final = 1;
+ default:
+ /*
+ * some sort of error, login terminated unsuccessfully,
+ * though this function did it's job.
+ * the caller must check the status_class and
+ * status_detail and decide what to do next.
+ */
+ ret = LOGIN_OK;
+ *final = 1;
+ }
+ return ret;
+}
+
+/**
+ * iscsi_login - attempt to login to the target.
+ * @session: login is initiated over this session
+ * @buffer: holds login pdu
+ * @bufsize: size of login pdu
+ * @status_class: holds either success or failure as status of login
+ * @status_detail: contains details based on the login status
+ *
+ * Description:
+ * The caller must check the status class to determine if the login
+ * succeeded. A return of 1 does not mean the login succeeded, it just
+ * means this function worked, and the status class is valid info.
+ * This allows the caller to decide whether or not to retry logins, so
+ * that we don't have any policy logic here.
+ **/
+enum iscsi_login_status
+iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize,
+ uint8_t * status_class, uint8_t * status_detail)
+{
+ struct iscsi_acl *auth_client = NULL;
+ struct iscsi_hdr pdu;
+ iscsi_login_rsp_t *login_rsp =
+ (iscsi_login_rsp_t *)&pdu;
+ char *data;
+ int received_pdu = 0;
+ int max_data_length;
+ int timeout = 0;
+ int final = 0;
+ enum iscsi_login_status ret = LOGIN_FAILED;
+
+ /* prepare the session */
+ session->cmdsn = 1;
+ session->exp_cmdsn = 1;
+ session->max_cmdsn = 1;
+ session->exp_statsn = 0;
+
+ session->current_stage = ISCSI_INITIAL_LOGIN_STAGE;
+ session->partial_response = 0;
+
+ if (session->auth_buffers && session->num_auth_buffers) {
+ ret = check_for_authentication(session, auth_client);
+ if (ret != LOGIN_OK)
+ return ret;
+ }
+
+ /*
+ * exchange PDUs until the login stage is complete, or an error occurs
+ */
+ do {
+ final = 0;
+ timeout = 0;
+ login_rsp = (iscsi_login_rsp_t *)&pdu;
+ ret = LOGIN_FAILED;
+
+ memset(buffer, 0, bufsize);
+ data = buffer;
+ max_data_length = bufsize;
+
+ /*
+ * pick the appropriate timeout. If we know the target has
+ * responded before, and we're in the security stage, we use a
+ * longer timeout, since the authentication alogorithms can
+ * take a while, especially if the target has to go talk to a
+ * tacacs or RADIUS server (which may or may not be
+ * responding).
+ */
+ if (received_pdu && (session->current_stage ==
+ ISCSI_SECURITY_NEGOTIATION_STAGE))
+ timeout = session->auth_timeout;
+ else
+ timeout = session->login_timeout;
+
+ /*
+ * fill in the PDU header and text data based on the login
+ * stage that we're in
+ */
+ if (!iscsi_make_login_pdu(session, &pdu, data,
+ max_data_length)) {
+ log_error("login failed, couldn't make "
+ "a login PDU\n");
+ ret = LOGIN_FAILED;
+ goto done;
+ }
+
+ /* send a PDU to the target */
+ if (!iscsi_send_pdu(session, &pdu, ISCSI_DIGEST_NONE,
+ data, ISCSI_DIGEST_NONE, timeout)) {
+ /*
+ * FIXME: caller might want us to distinguish I/O
+ * error and timeout. Might want to switch portals on
+ * timeouts, but
+ * not I/O errors.
+ */
+ log_error("Login I/O error, failed to "
+ "send a PDU\n");
+ ret = LOGIN_IO_ERROR;
+ goto done;
+ }
+
+ /* read the target's response into the same buffer */
+ if (!iscsi_recv_pdu(session, &pdu, ISCSI_DIGEST_NONE, data,
+ max_data_length, ISCSI_DIGEST_NONE,
+ timeout)) {
+ /*
+ * FIXME: caller might want us to distinguish I/O
+ * error and timeout. Might want to switch portals on
+ * timeouts, but not I/O errors.
+ */
+ log_error("Login I/O error, failed to "
+ "receive a PDU\n");
+ ret = LOGIN_IO_ERROR;
+ goto done;
+ }
+
+ received_pdu = 1;
+
+ /* check the PDU response type */
+ if (pdu.opcode == (ISCSI_OP_LOGIN_RSP | 0xC0)) {
+ /*
+ * it's probably a draft 8 login response,
+ * which we can't deal with
+ */
+ log_error("Received iSCSI draft 8 login "
+ "response opcode 0x%x, expected draft "
+ "20 login response 0x%2x\n",
+ pdu.opcode, ISCSI_OP_LOGIN_RSP);
+ ret = LOGIN_VERSION_MISMATCH;
+ goto done;
+ } else if (pdu.opcode != ISCSI_OP_LOGIN_RSP) {
+ ret = LOGIN_INVALID_PDU;
+ goto done;
+ }
+
+ /*
+ * give the caller the status class and detail from the last
+ * login response PDU received
+ */
+ if (status_class)
+ *status_class = login_rsp->status_class;
+ if (status_detail)
+ *status_detail = login_rsp->status_detail;
+ ret = check_status_login_response(session, login_rsp, data,
+ max_data_length, &final);
+ if (final)
+ goto done;
+ } while (session->current_stage != ISCSI_FULL_FEATURE_PHASE);
+
+ ret = LOGIN_OK;
+
+ done:
+ if (auth_client && acl_finish(auth_client) != AUTH_STATUS_NO_ERROR) {
+ log_error("Login failed, error finishing auth_client\n");
+ if (ret == LOGIN_OK)
+ ret = LOGIN_FAILED;
+ }
+
+ return ret;
+}
diff --git a/usr/md5.c b/usr/md5.c
new file mode 100644
index 0000000..2c7f9fa
--- /dev/null
+++ b/usr/md5.c
@@ -0,0 +1,236 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ * Changed so as no longer to depend on Colin Plumb's `usual.h' header
+ * definitions; now uses stuff from dpkg's config.h.
+ * - Ian Jackson <ijackson@nyx.cs.du.edu>.
+ * Still in the public domain.
+ */
+
+#include "md5.h"
+
+#ifdef WORDS_BIGENDIAN
+void
+byteSwap(UWORD32 *buf, unsigned words)
+{
+ md5byte *p = (md5byte *)buf;
+
+ do {
+ *buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 |
+ ((unsigned)p[1] << 8 | p[0]);
+ p += 4;
+ } while (--words);
+}
+#else
+#define byteSwap(buf,words)
+#endif
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bytes[0] = 0;
+ ctx->bytes[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len)
+{
+ UWORD32 t;
+
+ /* Update byte count */
+
+ t = ctx->bytes[0];
+ if ((ctx->bytes[0] = t + len) < t)
+ ctx->bytes[1]++; /* Carry from low to high */
+
+ t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
+ if (t > len) {
+ memcpy((md5byte *)ctx->in + 64 - t, buf, len);
+ return;
+ }
+ /* First chunk is an odd size */
+ memcpy((md5byte *)ctx->in + 64 - t, buf, t);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ buf += t;
+ len -= t;
+
+ /* Process data in 64-byte chunks */
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(md5byte digest[16], struct MD5Context *ctx)
+{
+ int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
+ md5byte *p = (md5byte *)ctx->in + count;
+
+ /* Set the first char of padding to 0x80. There is always room. */
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 56 bytes (-8..55) */
+ count = 56 - 1 - count;
+
+ if (count < 0) { /* Padding forces an extra block */
+ memset(p, 0, count + 8);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ p = (md5byte *)ctx->in;
+ count = 56;
+ }
+ memset(p, 0, count);
+ byteSwap(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ctx->in[14] = ctx->bytes[0] << 3;
+ ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
+ MD5Transform(ctx->buf, ctx->in);
+
+ byteSwap(ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f,w,x,y,z,in,s) \
+ (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void
+MD5Transform(UWORD32 buf[4], UWORD32 const in[16])
+{
+ register UWORD32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
diff --git a/usr/md5.h b/usr/md5.h
new file mode 100644
index 0000000..cdbbe2c
--- /dev/null
+++ b/usr/md5.h
@@ -0,0 +1,60 @@
+/*
+ * This is the header file for the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ * Changed so as no longer to depend on Colin Plumb's `usual.h'
+ * header definitions; now uses stuff from dpkg's config.h
+ * - Ian Jackson <ijackson@nyx.cs.du.edu>.
+ * Still in the public domain.
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+# define WORDS_BIGENDIAN 1
+#endif
+
+typedef uint32_t UWORD32;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define md5byte unsigned char
+
+struct MD5Context {
+ UWORD32 buf[4];
+ UWORD32 bytes[2];
+ UWORD32 in[16];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !MD5_H */
diff --git a/usr/sha1.c b/usr/sha1.c
new file mode 100644
index 0000000..8285a5e
--- /dev/null
+++ b/usr/sha1.c
@@ -0,0 +1,167 @@
+/*
+ * Cryptographic API.
+ *
+ * SHA1 Secure Hash Algorithm.
+ *
+ * Derived from cryptoapi implementation, adapted for in-place
+ * scatterlist interface. Originally based on the public domain
+ * implementation written by Steve Reid.
+ *
+ * Copyright (c) Alan Smithee.
+ * Copyright (c) Andrew McDonald <andrew@mcdonald.org.uk>
+ * Copyright (c) Jean-Francois Dive <jef@linuxbe.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ */
+#include "sha1.h"
+
+#define SHA1_DIGEST_SIZE 20
+#define SHA1_HMAC_BLOCK_SIZE 64
+
+static inline uint32_t rol(uint32_t value, uint32_t bits)
+{
+ return (((value) << (bits)) | ((value) >> (32 - (bits))));
+}
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+# define blk0(i) block32[i]
+
+#define blk(i) (block32[i&15] = rol(block32[(i+13)&15]^block32[(i+8)&15] \
+ ^block32[(i+2)&15]^block32[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5); \
+ w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5); \
+ w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5); \
+ w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+static void sha1_transform(uint32_t *state, const uint8_t *in)
+{
+ uint32_t a, b, c, d, e;
+ uint32_t block32[16];
+
+ /* convert/copy data to workspace */
+ for (a = 0; a < sizeof(block32)/sizeof(uint32_t); a++)
+ block32[a] = ntohl (((const uint32_t *)in)[a]);
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+ memset (block32, 0x00, sizeof block32);
+}
+
+void sha1_init(void *ctx)
+{
+ struct sha1_ctx *sctx = ctx;
+ static const struct sha1_ctx initstate = {
+ 0,
+ { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 },
+ { 0, }
+ };
+
+ *sctx = initstate;
+}
+
+void sha1_update(void *ctx, const uint8_t *data, unsigned int len)
+{
+ struct sha1_ctx *sctx = ctx;
+ unsigned int i, j;
+
+ j = (sctx->count >> 3) & 0x3f;
+ sctx->count += len << 3;
+
+ if ((j + len) > 63) {
+ memcpy(&sctx->buffer[j], data, (i = 64-j));
+ sha1_transform(sctx->state, sctx->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ sha1_transform(sctx->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ memcpy(&sctx->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+void sha1_final(void* ctx, uint8_t *out)
+{
+ struct sha1_ctx *sctx = ctx;
+ uint32_t i, j, index, padlen;
+ uint64_t t;
+ uint8_t bits[8] = { 0, };
+ static const uint8_t padding[64] = { 0x80, };
+
+ t = sctx->count;
+ bits[7] = 0xff & t; t>>=8;
+ bits[6] = 0xff & t; t>>=8;
+ bits[5] = 0xff & t; t>>=8;
+ bits[4] = 0xff & t; t>>=8;
+ bits[3] = 0xff & t; t>>=8;
+ bits[2] = 0xff & t; t>>=8;
+ bits[1] = 0xff & t; t>>=8;
+ bits[0] = 0xff & t;
+
+ /* Pad out to 56 mod 64 */
+ index = (sctx->count >> 3) & 0x3f;
+ padlen = (index < 56) ? (56 - index) : ((64+56) - index);
+ sha1_update(sctx, padding, padlen);
+
+ /* Append length */
+ sha1_update(sctx, bits, sizeof bits);
+
+ /* Store state in digest */
+ for (i = j = 0; i < 5; i++, j += 4) {
+ uint32_t t2 = sctx->state[i];
+ out[j+3] = t2 & 0xff; t2>>=8;
+ out[j+2] = t2 & 0xff; t2>>=8;
+ out[j+1] = t2 & 0xff; t2>>=8;
+ out[j ] = t2 & 0xff;
+ }
+
+ /* Wipe context */
+ memset(sctx, 0, sizeof *sctx);
+}
diff --git a/usr/sha1.h b/usr/sha1.h
new file mode 100644
index 0000000..af436b3
--- /dev/null
+++ b/usr/sha1.h
@@ -0,0 +1,27 @@
+/*
+ * sha1.h - SHA1 Secure Hash Algorithm used for CHAP authentication.
+ * copied from the Linux kernel's Cryptographic API and slightly adjusted to
+ * fit IET's needs
+ *
+ * This file is (c) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
+ * and licensed under the GPL.
+ */
+
+#ifndef SHA1_H
+#define SHA1_H
+
+#include <sys/types.h>
+#include <string.h>
+#include "types.h"
+
+struct sha1_ctx {
+ uint64_t count;
+ uint32_t state[5];
+ uint8_t buffer[64];
+};
+
+void sha1_init(void *ctx);
+void sha1_update(void *ctx, const uint8_t *data, unsigned int len);
+void sha1_final(void* ctx, uint8_t *out);
+
+#endif
diff --git a/usr/types.h b/usr/types.h
new file mode 100644
index 0000000..18961e6
--- /dev/null
+++ b/usr/types.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <netinet/in.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <byteswap.h>
+#include <endian.h>
+
+#endif /* TYPES_H */