diff options
-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.h | 600 | ||||
-rw-r--r-- | include/iscsi_u.h | 34 | ||||
-rwxr-xr-x | iscsiadm | 988 | ||||
-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-x | test/regression.sh (renamed from regression.sh) | 0 | ||||
-rw-r--r-- | usr/Makefile | 13 | ||||
-rw-r--r-- | usr/auth.c | 2078 | ||||
-rw-r--r-- | usr/auth.h | 285 | ||||
-rw-r--r-- | usr/chap.c | 652 | ||||
-rw-r--r-- | usr/ctldev.c | 89 | ||||
-rw-r--r-- | usr/initiator.h | 151 | ||||
-rw-r--r-- | usr/io.c | 524 | ||||
-rw-r--r-- | usr/ipc.c | 116 | ||||
-rw-r--r-- | usr/iscsiadm.c | 82 | ||||
-rw-r--r-- | usr/iscsiadm.h | 49 | ||||
-rw-r--r-- | usr/iscsid.c | 238 | ||||
-rw-r--r-- | usr/iscsid.h | 50 | ||||
-rw-r--r-- | usr/log.c | 127 | ||||
-rw-r--r-- | usr/log.h | 41 | ||||
-rw-r--r-- | usr/login.c | 1444 | ||||
-rw-r--r-- | usr/md5.c | 236 | ||||
-rw-r--r-- | usr/md5.h | 60 | ||||
-rw-r--r-- | usr/sha1.c | 167 | ||||
-rw-r--r-- | usr/sha1.h | 27 | ||||
-rw-r--r-- | usr/types.h | 16 |
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 */ |