blob: b129f09558150dd7a7da1e367d520fc2662cb8da [file] [log] [blame]
/*
* Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "iscsid.h"
int iscsi_enabled;
static u32 ttt;
static u32 get_next_ttt(struct connection *conn __attribute__((unused)))
{
ttt += 1;
return (ttt == ISCSI_RESERVED_TAG) ? ++ttt : ttt;
}
static struct iscsi_key login_keys[] = {
{ .name = "InitiatorName",},
{ .name = "InitiatorAlias",},
{ .name = "SessionType",},
{ .name = "TargetName",},
{ .name = "InitiatorRecvDataSegmentLength",},
{ .name = "MaxAHSLength",},
{ .name = "TaggedBufferForSolicitedDataOnly",},
{ .name = "iSERHelloRequired",},
{ .name = NULL,},
};
char *text_key_find(struct connection *conn, const char *searchKey)
{
char *data, *key, *value;
int keylen, datasize;
keylen = strlen(searchKey);
data = conn->req.data;
datasize = conn->req.datasize;
while (1) {
for (key = data; datasize > 0 && *data != '='; data++, datasize--)
;
if (!datasize)
return NULL;
data++;
datasize--;
for (value = data; datasize > 0 && *data != 0; data++, datasize--)
;
if (!datasize)
return NULL;
data++;
datasize--;
if (keylen == value - key - 1
&& !strncasecmp(key, searchKey, keylen))
return value;
}
}
static char *next_key(char **data, int *datasize, char **value)
{
char *key, *p, *q;
int size = *datasize;
key = p = *data;
for (; size > 0 && *p != '='; p++, size--)
;
if (!size)
return NULL;
*p++ = 0;
size--;
for (q = p; size > 0 && *p != 0; p++, size--)
;
if (!size)
return NULL;
p++;
size--;
*data = p;
*value = q;
*datasize = size;
return key;
}
static struct buf_segment *conn_alloc_buf_segment(struct connection *conn,
size_t sz)
{
struct buf_segment *seg = malloc(sizeof(*seg) + sz);
if (seg) {
seg->len = 0;
memset(seg->data, 0x0, sz);
list_add_tail(&seg->entry, &conn->rsp_buf_list);
log_debug(2, "alloc'ed new buf_segment");
}
return seg;
}
void text_key_add(struct connection *conn, const char *key, const char *value)
{
struct buf_segment *seg;
int keylen = strlen(key);
int valuelen = strlen(value);
int len = keylen + valuelen + 2;
int off = 0;
int sz = 0;
int stage = 0;
size_t data_sz;
data_sz = (conn->state == STATE_FULL) ?
conn->session_params[key_max_xmit_data_length].val :
INCOMING_BUFSIZE;
seg = list_empty(&conn->rsp_buf_list) ? NULL :
list_entry(conn->rsp_buf_list.q_back, struct buf_segment,
entry);
while (len) {
if (!seg || seg->len == data_sz) {
seg = conn_alloc_buf_segment(conn, data_sz);
if (!seg) {
log_error("Failed to alloc text buf segment\n");
conn_free_rsp_buf_list(conn);
break;
}
}
switch (stage) {
case 0:
sz = min_t(int, data_sz - seg->len, keylen - off);
strncpy(seg->data + seg->len, key + off, sz);
if (sz == data_sz - seg->len) {
off += sz;
if (keylen - off == 0) {
off = 0;
stage++;
}
} else {
off = 0;
stage++;
}
break;
case 1:
seg->data[seg->len] = '=';
off = 0;
sz = 1;
stage++;
break;
case 2:
sz = min_t(int, data_sz - seg->len, valuelen - off);
strncpy(seg->data + seg->len, value + off, sz);
off += sz;
if (valuelen - off == 0) {
off = 0;
stage++;
}
break;
case 3:
seg->data[seg->len] = 0;
sz = 1;
break;
}
log_debug(2, "wrote: %s", seg->data + seg->len);
seg->len += sz;
len -= sz;
}
}
static void text_key_add_reject(struct connection *conn, char *key)
{
text_key_add(conn, key, "Reject");
}
static void login_rsp_ini_err(struct connection *conn, int status_detail)
{
struct iscsi_login_rsp_hdr * const rsp =
(struct iscsi_login_rsp_hdr * const)&conn->rsp.bhs;
rsp->status_class = ISCSI_STATUS_INITIATOR_ERR;
rsp->status_detail = status_detail;
conn->state = STATE_EXIT;
return;
}
static void login_rsp_tgt_err(struct connection *conn, int status_detail)
{
struct iscsi_login_rsp_hdr * const rsp =
(struct iscsi_login_rsp_hdr * const)&conn->rsp.bhs;
rsp->status_class = ISCSI_STATUS_TARGET_ERR;
rsp->status_detail = status_detail;
conn->state = STATE_EXIT;
return;
}
static void text_scan_security(struct connection *conn)
{
char *key, *value, *data, *nextValue;
int datasize;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!(params_index_by_name(key, login_keys) < 0))
;
else if (!strcmp(key, "AuthMethod")) {
do {
nextValue = strchr(value, ',');
if (nextValue)
*nextValue++ = 0;
if (!strcmp(value, "None")) {
if (!accounts_empty(conn->tid, ISCSI_USER_DIR_INCOMING))
continue;
conn->auth_method = AUTH_NONE;
text_key_add(conn, key, "None");
break;
} else if (!strcmp(value, "CHAP")) {
if (accounts_empty(conn->tid, ISCSI_USER_DIR_INCOMING))
continue;
conn->auth_method = AUTH_CHAP;
text_key_add(conn, key, "CHAP");
break;
}
} while ((value = nextValue));
if (conn->auth_method == AUTH_UNKNOWN)
text_key_add_reject(conn, key);
} else
text_key_add(conn, key, "NotUnderstood");
}
if (conn->auth_method == AUTH_UNKNOWN)
login_rsp_ini_err(conn, ISCSI_STATUS_AUTH_FAILED);
return;
}
#define ISCSI_SESS_REINSTATEMENT 1
#define ISCSI_CONN_REINSTATEMENT 2
/*
* Returns above ISCSI_*_REINSTATEMENT for session reinstatement,
* <0 for error, 0 otherwise.
*/
static int login_check_reinstatement(struct connection *conn)
{
struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs;
struct session *session;
int res = 0;
/*
* We only check here to catch errors earlier. Actual session/connection
* reinstatement, if necessary, will be done in the kernel.
*/
sBUG_ON(conn->sess != NULL);
session = session_find_name(conn->tid, conn->initiator, req->sid);
if (session != NULL) {
if (req->sid.id.tsih == 0) {
struct connection *existing_conn;
list_for_each_entry(existing_conn, &session->conn_list, clist) {
/* Don't go for session reinstatement if IPs are different */
if(strcmp(conn->initiator_ip_address, existing_conn->initiator_ip_address)) {
log_info("Session sid %#" PRIx64 " Reinstatement "
"rejected (tid %d, initiator %s, IPs [%s, %s])", req->sid.id64,
conn->tid, conn->initiator, conn->initiator_ip_address, existing_conn->initiator_ip_address);
/* Fail the login */
login_rsp_ini_err(conn, ISCSI_STATUS_DUP_IQN);
res = -1;
goto out;
}
}
/* Kernel will do session reinstatement */
log_info("Session sid %#" PRIx64 " reinstatement "
"detected (tid %d, initiator %s)", req->sid.id64,
conn->tid, conn->initiator);
res = ISCSI_SESS_REINSTATEMENT;
} else if (req->sid.id.tsih != session->sid.id.tsih) {
log_error("TSIH for existing session sid %#" PRIx64
") doesn't match (tid %d, initiator %s, sid requested "
"%#" PRIx64, session->sid.id64, conn->tid,
conn->initiator, req->sid.id64);
/* Fail the login */
login_rsp_ini_err(conn, ISCSI_STATUS_SESSION_NOT_FOUND);
res = -1;
goto out;
} else {
struct connection *c = conn_find(session, conn->cid);
if (c != NULL) {
/* Kernel will do connection reinstatement */
log_debug(1, "Conn %x reinstatement "
"detected (tid %d, sid %#" PRIx64
"initiator %s)", conn->cid, conn->tid,
req->sid.id64, conn->initiator);
conn->sess = session;
list_add_tail(&conn->clist, &session->conn_list);
res = ISCSI_CONN_REINSTATEMENT;
} else {
log_error("Only a single connection supported "
"(initiator %s)", conn->initiator);
/* Fail the login */
login_rsp_ini_err(conn, ISCSI_STATUS_TOO_MANY_CONN);
res = -1;
goto out;
}
}
} else {
if (req->sid.id.tsih != 0) {
log_error("Requested TSIH not 0 (TSIH %d, tid %d, "
"initiator %s, sid requisted %#" PRIx64 ")",
req->sid.id.tsih, conn->tid, conn->initiator,
req->sid.id64);
/* Fail the login */
login_rsp_ini_err(conn, ISCSI_STATUS_SESSION_NOT_FOUND);
res = -1;
goto out;
} else
log_debug(1, "New session sid %#" PRIx64 "(tid %d, "
"initiator %s)", req->sid.id64,
conn->tid, conn->initiator);
}
out:
return res;
}
static void text_scan_login(struct connection *conn)
{
char *key, *value, *data;
int datasize, idx;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!(params_index_by_name(key, login_keys) < 0))
;
else if (!strcmp(key, "AuthMethod"))
;
else if (!((idx = params_index_by_name(key, session_keys)) < 0)) {
unsigned int val;
char buf[32];
if (idx == key_max_xmit_data_length) {
text_key_add(conn, key, "NotUnderstood");
continue;
}
if (idx == key_max_recv_data_length) {
conn->session_params[idx].key_state = KEY_STATE_DONE;
idx = key_max_xmit_data_length;
};
if (params_str_to_val(session_keys, idx, value, &val) < 0) {
if (conn->session_params[idx].key_state == KEY_STATE_START) {
text_key_add_reject(conn, key);
continue;
} else {
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
goto out;
}
}
if (conn->is_iser) {
switch (idx) {
case key_rdma_extensions:
if (val != 1) {
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
goto out;
}
break;
case key_initial_r2t:
val = 1;
break;
case key_immediate_data:
val = 0;
break;
}
} else if (idx == key_rdma_extensions && val != 0) {
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
goto out;
}
params_check_val(session_keys, idx, &val);
params_set_val(session_keys, conn->session_params, idx, &val);
switch (conn->session_params[idx].key_state) {
case KEY_STATE_START:
if (iscsi_is_key_internal(idx)) {
conn->session_params[idx].key_state = KEY_STATE_DONE;
break;
}
memset(buf, 0, sizeof(buf));
params_val_to_str(session_keys, idx, val, buf, sizeof(buf));
text_key_add(conn, key, buf);
conn->session_params[idx].key_state = KEY_STATE_DONE_ADDED;
break;
case KEY_STATE_REQUEST:
if (val != conn->session_params[idx].val) {
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
log_warning("%s %u %u\n", key,
val, conn->session_params[idx].val);
goto out;
}
conn->session_params[idx].key_state = KEY_STATE_DONE;
break;
case KEY_STATE_DONE_ADDED:
case KEY_STATE_DONE:
break;
}
} else
text_key_add(conn, key, "NotUnderstood");
}
out:
return;
}
static int text_check_params(struct connection *conn)
{
struct iscsi_param *p = conn->session_params;
char buf[32];
int i, cnt;
for (i = 0, cnt = 0; session_keys[i].name; i++) {
if (p[i].val != session_keys[i].rfc_def) {
if (p[i].key_state == KEY_STATE_START) {
log_debug(1, "Key %s was not negotiated, use RFC "
"defined default %d", session_keys[i].name,
session_keys[i].rfc_def);
p[i].val = session_keys[i].rfc_def;
continue;
} else if (p[i].key_state == KEY_STATE_DONE_ADDED) {
log_debug(1, "Key %s was already added, val %d",
session_keys[i].name, p[i].val);
continue;
}
switch (conn->state) {
case STATE_LOGIN_FULL:
case STATE_SECURITY_FULL:
if (iscsi_is_key_internal(i)) {
p[i].key_state = KEY_STATE_DONE;
continue;
}
break;
case STATE_LOGIN:
if (iscsi_is_key_internal(i))
continue;
memset(buf, 0, sizeof(buf));
params_val_to_str(session_keys, i, p[i].val, buf, sizeof(buf));
text_key_add(conn, session_keys[i].name, buf);
if (i == key_max_recv_data_length) {
p[i].key_state = KEY_STATE_DONE;
continue;
}
p[i].key_state = KEY_STATE_REQUEST;
break;
default:
if (iscsi_is_key_internal(i))
continue;
}
cnt++;
}
}
return cnt;
}
static int init_conn_session_params(struct connection *conn)
{
int res = 0, i;
struct target *target;
target = target_find_by_id(conn->tid);
if (target == NULL) {
log_error("target %d not found", conn->tid);
res = -ENOENT;
goto out;
}
for (i = 0; i < session_key_last; i++)
conn->session_params[i].val = target->session_params[i];
out:
return res;
}
static void login_start(struct connection *conn)
{
struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs;
char *name, *session_type, *target_name;
conn->cid = be16_to_cpu(req->cid);
conn->sid.id64 = req->sid.id64;
name = text_key_find(conn, "InitiatorName");
if (!name) {
login_rsp_ini_err(conn, ISCSI_STATUS_MISSING_FIELDS);
return;
}
conn->initiator = strdup(name);
if (conn->initiator == NULL) {
log_error("Unable to duplicate initiator's name %s", name);
login_rsp_tgt_err(conn, ISCSI_STATUS_NO_RESOURCES);
return;
}
session_type = text_key_find(conn, "SessionType");
target_name = text_key_find(conn, "TargetName");
conn->auth_method = -1;
conn->session_type = SESSION_NORMAL;
if (session_type) {
if (!strcmp(session_type, "Discovery")) {
int ret = conn->is_discovery(conn->fd);
if (ret) {
login_rsp_tgt_err(conn, ISCSI_STATUS_MISSING_FIELDS);
return;
}
conn->session_type = SESSION_DISCOVERY;
} else if (strcmp(session_type, "Normal")) {
login_rsp_ini_err(conn, ISCSI_STATUS_INV_SESSION_TYPE);
return;
}
}
if (conn->session_type == SESSION_NORMAL) {
struct target *target;
int err, rc;
if (!target_name) {
login_rsp_ini_err(conn, ISCSI_STATUS_MISSING_FIELDS);
return;
}
target = target_find_by_name(target_name);
if (target == NULL) {
login_rsp_ini_err(conn, ISCSI_STATUS_TGT_NOT_FOUND);
return;
}
conn->target = target;
/* We may "leak" here if we have an iSCSI event on the wrong time */
if (!iscsi_enabled) {
log_info("Connect from %s to disabled iSCSI-SCST refused",
name);
login_rsp_tgt_err(conn, 0);
conn->state = STATE_DROP;
return;
}
if (!target->tgt_enabled) {
log_info("Connect from %s to disabled target %s refused",
name, target_name);
login_rsp_tgt_err(conn, 0);
conn->state = STATE_DROP;
return;
}
conn->tid = target->tid;
if (!config_initiator_access_allowed(conn->tid, conn->fd) ||
!target_portal_allowed(target, conn->target_portal,
conn->initiator) ||
!isns_scn_access_allowed(conn->tid, name)) {
log_info("Initiator %s not allowed to connect to "
"target %s", name, target_name);
login_rsp_ini_err(conn, ISCSI_STATUS_TGT_NOT_FOUND);
return;
}
if (target_redirected(target, conn)) {
struct iscsi_login_rsp_hdr *rsp =
(struct iscsi_login_rsp_hdr *)&conn->rsp.bhs;
log_debug(1, "Redirecting target %s login to %s:%d",
target->name, target->redirect.addr,
target->redirect.port);
rsp->status_class = ISCSI_STATUS_REDIRECT;
rsp->status_detail = target->redirect.type;
conn->state = STATE_EXIT;
return;
}
err = init_conn_session_params(conn);
if (err != 0) {
log_error("Can't get session params for session 0x%" PRIx64
" (err %d): %s\n", conn->sid.id64, err,
strerror(-err));
login_rsp_tgt_err(conn, ISCSI_STATUS_TARGET_ERROR);
return;
}
rc = login_check_reinstatement(conn);
if (rc < 0)
return;
else if (rc == ISCSI_SESS_REINSTATEMENT) {
target->sessions_count++;
conn->sessions_count_incremented = 1;
} else if (rc != ISCSI_CONN_REINSTATEMENT) {
if ((target->target_params[key_max_sessions] == 0) ||
(target->sessions_count < target->target_params[key_max_sessions])) {
target->sessions_count++;
conn->sessions_count_incremented = 1;
} else {
log_warning("Initiator %s not allowed to connect to "
"target %s - max sessions limit "
"reached (%d)", name, target_name,
target->target_params[key_max_sessions]);
login_rsp_tgt_err(conn, ISCSI_STATUS_NO_RESOURCES);
conn->state = STATE_EXIT;
return;
}
}
log_debug(1, "target %s, sessions_count %d", target_name,
target->sessions_count);
}
conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn);
log_debug(1, "exp_cmd_sn %u, cmd_sn %u", conn->exp_cmd_sn, req->cmd_sn);
text_key_add(conn, "TargetPortalGroupTag", "1");
return;
}
static int login_finish(struct connection *conn)
{
int res = 0;
if (conn->is_iser &&
conn->session_params[key_target_recv_data_length].key_state == KEY_STATE_START) {
char buf[32] = "\0";
params_val_to_str(session_keys, key_target_recv_data_length,
session_keys[key_target_recv_data_length].local_def,
buf, sizeof(buf));
text_key_add(conn, "TargetRecvDataSegmentLength", buf);
}
switch (conn->session_type) {
case SESSION_NORMAL:
if (!conn->sess)
res = session_create(conn);
if (res == 0)
conn->sid = conn->sess->sid;
break;
case SESSION_DISCOVERY:
/* set a dummy tsih value */
conn->sid.id.tsih = 1;
break;
}
return res;
}
static void cmnd_reject(struct connection *conn, u8 reason)
{
struct iscsi_reject_hdr *rej =
(struct iscsi_reject_hdr *)&conn->rsp.bhs;
size_t data_sz = sizeof(struct iscsi_hdr);
struct buf_segment *seg;
conn_free_rsp_buf_list(conn);
seg = conn_alloc_buf_segment(conn, data_sz);
memset(rej, 0x0, sizeof(*rej));
rej->opcode = ISCSI_OP_REJECT_MSG;
rej->reason = reason;
rej->ffffffff = ISCSI_RESERVED_TAG;
rej->flags |= ISCSI_FLG_FINAL;
rej->stat_sn = cpu_to_be32(conn->stat_sn++);
rej->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
rej->max_cmd_sn = cpu_to_be32(conn->exp_cmd_sn + 1);
if (!seg) {
log_error("Failed to alloc data segment for Reject PDU\n");
return;
}
memcpy(seg->data, &conn->req.bhs, data_sz);
seg->len = data_sz;
}
static int cmnd_exec_auth(struct connection *conn)
{
int res;
switch (conn->auth_method) {
case AUTH_CHAP:
res = cmnd_exec_auth_chap(conn);
break;
case AUTH_NONE:
res = 0;
break;
default:
log_error("Unknown auth. method %d", conn->auth_method);
res = -3;
}
return res;
}
static void cmnd_exec_login(struct connection *conn)
{
struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs;
struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs;
int stay = 0, nsg_disagree = 0;
memset(rsp, 0, BHS_SIZE);
if ((req->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN_CMD ||
!(req->opcode & ISCSI_OP_IMMEDIATE)) {
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
return;
}
rsp->opcode = ISCSI_OP_LOGIN_RSP;
rsp->max_version = ISCSI_VERSION;
rsp->active_version = ISCSI_VERSION;
rsp->itt = req->itt;
if (/*req->max_version < ISCSI_VERSION ||*/
req->min_version > ISCSI_VERSION) {
login_rsp_ini_err(conn, ISCSI_STATUS_NO_VERSION);
return;
}
switch (req->flags & ISCSI_FLG_CSG_MASK) {
case ISCSI_FLG_CSG_SECURITY:
log_debug(1, "Login request (security negotiation): %d", conn->state);
rsp->flags = ISCSI_FLG_CSG_SECURITY;
switch (conn->state) {
case STATE_FREE:
conn->state = STATE_SECURITY;
login_start(conn);
if (rsp->status_class)
return;
/* else fall through */
case STATE_SECURITY:
text_scan_security(conn);
if (rsp->status_class)
return;
if (conn->auth_method != AUTH_NONE) {
conn->state = STATE_SECURITY_AUTH;
conn->auth_state = AUTH_STATE_START;
}
break;
case STATE_SECURITY_AUTH:
switch (cmnd_exec_auth(conn)) {
case 0:
break;
default:
case -1:
goto init_err;
case -2:
goto auth_err;
}
break;
default:
goto init_err;
}
break;
case ISCSI_FLG_CSG_LOGIN:
log_debug(1, "Login request (operational negotiation): %d", conn->state);
rsp->flags = ISCSI_FLG_CSG_LOGIN;
switch (conn->state) {
case STATE_FREE:
conn->state = STATE_LOGIN;
login_start(conn);
if (rsp->status_class)
return;
if (!accounts_empty(conn->tid, ISCSI_USER_DIR_INCOMING))
goto auth_err;
if (rsp->status_class)
return;
text_scan_login(conn);
if (rsp->status_class)
return;
stay = text_check_params(conn);
break;
case STATE_LOGIN:
text_scan_login(conn);
if (rsp->status_class)
return;
stay = text_check_params(conn);
break;
default:
goto init_err;
}
break;
default:
goto init_err;
}
if (rsp->status_class)
return;
if (conn->state != STATE_SECURITY_AUTH && req->flags & ISCSI_FLG_TRANSIT) {
int nsg = req->flags & ISCSI_FLG_NSG_MASK;
switch (nsg) {
case ISCSI_FLG_NSG_LOGIN:
switch (conn->state) {
case STATE_SECURITY:
case STATE_SECURITY_DONE:
conn->state = STATE_SECURITY_LOGIN;
break;
default:
goto init_err;
}
break;
case ISCSI_FLG_NSG_FULL_FEATURE:
switch (conn->state) {
case STATE_SECURITY:
case STATE_SECURITY_DONE:
if ((nsg_disagree = text_check_params(conn))) {
conn->state = STATE_LOGIN;
nsg = ISCSI_FLG_NSG_LOGIN;
break;
}
conn->state = STATE_SECURITY_FULL;
break;
case STATE_LOGIN:
if (stay)
nsg = ISCSI_FLG_NSG_LOGIN;
else
conn->state = STATE_LOGIN_FULL;
break;
default:
goto init_err;
}
if (!stay && !nsg_disagree) {
int err;
text_check_params(conn);
err = login_finish(conn);
if (err != 0) {
log_debug(1, "login_finish() failed: %d", err);
/* Make initiator retry later */
goto tgt_no_mem;
}
}
break;
default:
goto init_err;
}
rsp->flags |= nsg | (stay ? 0 : ISCSI_FLG_TRANSIT);
}
/*
* TODO: support Logical Text Data Segments > INCOMING_BUFSIZE (i.e.
* key=value pairs spanning several PDUs) during login phase
*/
if (!list_empty(&conn->rsp_buf_list) &&
!list_length_is_one(&conn->rsp_buf_list)) {
log_error("Target error: \'key=value\' pairs spanning several "
"Login PDUs are not implemented, yet\n");
goto target_err;
}
rsp->sid = conn->sid;
rsp->stat_sn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmd_sn = cpu_to_be32(conn->exp_cmd_sn + 1);
return;
init_err:
log_error("Initiator %s error", conn->initiator);
rsp->flags = 0;
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
return;
auth_err:
log_error("Authentication of initiator %s failed", conn->initiator);
rsp->flags = 0;
login_rsp_ini_err(conn, ISCSI_STATUS_AUTH_FAILED);
return;
target_err:
rsp->flags = 0;
login_rsp_tgt_err(conn, ISCSI_STATUS_TARGET_ERROR);
return;
tgt_no_mem:
rsp->flags = 0;
login_rsp_tgt_err(conn, ISCSI_STATUS_NO_RESOURCES);
return;
}
static int text_scan_text(struct connection *conn)
{
int res = 0;
char *key, *value, *data;
int datasize;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!strcmp(key, "SendTargets")) {
if (value[0] == '\0')
value = conn->sess->target->name;
else if (strcasecmp(value, "All") == 0) {
if (conn->session_type != SESSION_DISCOVERY) {
log_error("SendTargets=All allowed only in "
"Discovery session, rejecting "
"(initiator %s)", conn->initiator);
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
res = -EINVAL;
goto out;
}
}
target_list_build(conn,
strcmp(value, "All") ? value : NULL);
} else
text_key_add(conn, key, "NotUnderstood");
}
out:
return res;
}
static void cmnd_exec_text(struct connection *conn)
{
struct iscsi_text_req_hdr *req = (struct iscsi_text_req_hdr *)&conn->req.bhs;
struct iscsi_text_rsp_hdr *rsp = (struct iscsi_text_rsp_hdr *)&conn->rsp.bhs;
int rc;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_TEXT_RSP;
rsp->itt = req->itt;
conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn);
if (!(req->opcode & ISCSI_OP_IMMEDIATE))
conn->exp_cmd_sn++;
log_debug(1, "Text request: %d", conn->state);
if (req->ttt == ISCSI_RESERVED_TAG) {
conn_free_rsp_buf_list(conn);
rc = text_scan_text(conn);
if (rc != 0)
goto out;
if (!list_empty(&conn->rsp_buf_list) &&
!list_length_is_one(&conn->rsp_buf_list))
conn->ttt = get_next_ttt(conn);
else
conn->ttt = ISCSI_RESERVED_TAG;
} else if (list_empty(&conn->rsp_buf_list) || conn->ttt != req->ttt) {
log_error("Rejecting unexpected text request. TTT recv %#x, "
"expected %#x; %stext segments queued\n",
req->ttt, conn->ttt, list_empty(&conn->rsp_buf_list) ?
"no " : "");
cmnd_reject(conn, ISCSI_REASON_INVALID_PDU_FIELD);
return;
}
if (list_empty(&conn->rsp_buf_list) ||
list_length_is_one(&conn->rsp_buf_list)) {
rsp->flags = ISCSI_FLG_FINAL;
conn->ttt = ISCSI_RESERVED_TAG;
}
rsp->ttt = conn->ttt;
rsp->stat_sn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmd_sn = cpu_to_be32(conn->exp_cmd_sn + 1);
out:
return;
}
static void cmnd_exec_logout(struct connection *conn)
{
struct iscsi_logout_req_hdr *req = (struct iscsi_logout_req_hdr *)&conn->req.bhs;
struct iscsi_logout_rsp_hdr *rsp = (struct iscsi_logout_rsp_hdr *)&conn->rsp.bhs;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_LOGOUT_RSP;
rsp->flags = ISCSI_FLG_FINAL;
rsp->itt = req->itt;
conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn);
if (!(req->opcode & ISCSI_OP_IMMEDIATE))
conn->exp_cmd_sn++;
rsp->stat_sn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmd_sn = cpu_to_be32(conn->exp_cmd_sn + 1);
}
int cmnd_execute(struct connection *conn)
{
int res = 1;
struct buf_segment *seg;
struct iscsi_login_rsp_hdr *login_rsp;
switch (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_LOGIN_CMD:
if (conn->state == STATE_FULL) {
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
break;
}
cmnd_exec_login(conn);
login_rsp = (struct iscsi_login_rsp_hdr *) &conn->rsp.bhs;
if (login_rsp->status_class && login_rsp->status_class != ISCSI_STATUS_REDIRECT)
conn_free_rsp_buf_list(conn);
break;
case ISCSI_OP_TEXT_CMD:
if (conn->state != STATE_FULL)
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
else
cmnd_exec_text(conn);
break;
case ISCSI_OP_LOGOUT_CMD:
if (conn->state != STATE_FULL)
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
else
cmnd_exec_logout(conn);
break;
default:
cmnd_reject(conn, ISCSI_REASON_UNSUPPORTED_COMMAND);
res = 0;
goto out;
}
if (!list_empty(&conn->rsp_buf_list)) {
seg = list_entry(conn->rsp_buf_list.q_forw,
struct buf_segment, entry);
list_del_init(&seg->entry);
conn->rsp.datasize = seg->len;
conn->rsp.data = seg->data;
} else {
conn->rsp.datasize = 0;
conn->rsp.data = NULL;
}
conn->rsp.bhs.ahslength = conn->rsp.ahssize / 4;
conn->rsp.bhs.datalength[0] = conn->rsp.datasize >> 16;
conn->rsp.bhs.datalength[1] = conn->rsp.datasize >> 8;
conn->rsp.bhs.datalength[2] = conn->rsp.datasize;
log_pdu(2, &conn->rsp);
out:
return res;
}
void cmnd_finish(struct connection *conn)
{
struct buf_segment *seg;
if (conn->rsp.data) {
seg = container_of(conn->rsp.data, struct buf_segment, data);
list_del(&seg->entry);
free(seg);
conn->rsp.data = NULL;
}
switch (conn->state) {
case STATE_EXIT:
case STATE_DROP:
break;
case STATE_SECURITY_LOGIN:
conn->state = STATE_LOGIN;
break;
case STATE_SECURITY_FULL:
/* fall through */
case STATE_LOGIN_FULL:
if (conn->session_type == SESSION_NORMAL)
conn->state = STATE_KERNEL;
else
conn->state = STATE_FULL;
break;
}
}