mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
SIGUSR1/SIGUSR2 set/clear logins_suspended. While set, any login attempt is rejected with a retriable Target Error instead of the permanent Initiator Error (TGT_NOT_FOUND) that causes initiators to give up.
1426 lines
37 KiB
C
1426 lines
37 KiB
C
/*
|
|
* 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;
|
|
char *internal_portal;
|
|
int logins_suspended;
|
|
|
|
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) {
|
|
/* Kernel will do session reinstatement */
|
|
log_debug(1, "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 bool valid_ascii_name_char(char ch)
|
|
{
|
|
|
|
/*
|
|
* From rtf3722
|
|
*
|
|
* For those using only ASCII characters (U+0000 to U+007F), the
|
|
* following characters are allowed:
|
|
*
|
|
* - ASCII dash character ('-' = U+002d)
|
|
* - ASCII dot character ('.' = U+002e)
|
|
* - ASCII colon character (':' = U+003a)
|
|
* - ASCII lower-case characters ('a'..'z' = U+0061..U+007a)
|
|
* - ASCII digit characters ('0'..'9' = U+0030..U+0039)
|
|
*/
|
|
|
|
if ((ch == '-') || (ch == '.') || (ch == ':') ||
|
|
((ch >= 'a') && (ch <= 'z')) ||
|
|
((ch >= '0') && (ch <= '9')))
|
|
return true;
|
|
|
|
log_error("Name contains invalid character: %c", ch);
|
|
return false;
|
|
}
|
|
|
|
static bool valid_utf8_name_char(long ch)
|
|
{
|
|
/*
|
|
* From rfc3722 6. Prohibited Output
|
|
*
|
|
* This profile specifies prohibiting using the following tables from
|
|
* [RFC3454]. Characters appearing within these tables MUST NOT be used
|
|
* within an iSCSI name.
|
|
*
|
|
* Table C.1.1
|
|
* Table C.1.2
|
|
* Table C.2.1
|
|
* Table C.2.2
|
|
* Table C.3
|
|
* Table C.4
|
|
* Table C.5
|
|
* Table C.6
|
|
* Table C.7
|
|
* Table C.8
|
|
* Table C.9
|
|
*/
|
|
|
|
/* C.1.1 ASCII space characters */
|
|
if (ch == 0x0020) {
|
|
log_error("Invalid UTF-8: %lX (ASCII space characters)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.1.2 Non-ASCII space characters */
|
|
switch (ch) {
|
|
case 0x00A0: /* NO-BREAK SPACE */
|
|
case 0x1680: /* OGHAM SPACE MARK */
|
|
case 0x2000: /* EN QUAD */
|
|
case 0x2001: /* EM QUAD */
|
|
case 0x2002: /* EN SPACE */
|
|
case 0x2003: /* EM SPACE */
|
|
case 0x2004: /* THREE-PER-EM SPACE */
|
|
case 0x2005: /* FOUR-PER-EM SPACE */
|
|
case 0x2006: /* SIX-PER-EM SPACE */
|
|
case 0x2007: /* FIGURE SPACE */
|
|
case 0x2008: /* PUNCTUATION SPACE */
|
|
case 0x2009: /* THIN SPACE */
|
|
case 0x200A: /* HAIR SPACE */
|
|
case 0x200B: /* ZERO WIDTH SPACE */
|
|
case 0x202F: /* NARROW NO-BREAK SPACE */
|
|
case 0x205F: /* MEDIUM MATHEMATICAL SPACE */
|
|
case 0x3000: /* IDEOGRAPHIC SPACE */
|
|
log_error("Invalid UTF-8: %lX (Non-ASCII space characters)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.2.1 ASCII control characters */
|
|
if ((ch >= 0x0000 && ch <= 0x001F) || /* CONTROL CHARACTERS */
|
|
(ch == 0x007F)) { /* DELETE */
|
|
log_error("Invalid UTF-8: %lX (ASCII control characters)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.2.2 Non-ASCII control characters */
|
|
if ((ch >= 0x0080 && ch <= 0x009F) || /* CONTROL CHARACTERS */
|
|
(ch == 0x06DD) || /* ARABIC END OF AYAH */
|
|
(ch == 0x070F) || /* SYRIAC ABBREVIATION MARK */
|
|
(ch == 0x180E) || /* MONGOLIAN VOWEL SEPARATOR */
|
|
(ch == 0x200C) || /* ZERO WIDTH NON-JOINER */
|
|
(ch == 0x200D) || /* ZERO WIDTH JOINER */
|
|
(ch == 0x2028) || /* LINE SEPARATOR */
|
|
(ch == 0x2029) || /* PARAGRAPH SEPARATOR */
|
|
(ch == 0x2060) || /* WORD JOINER */
|
|
(ch == 0x2061) || /* FUNCTION APPLICATION */
|
|
(ch == 0x2062) || /* INVISIBLE TIMES */
|
|
(ch == 0x2063) || /* INVISIBLE SEPARATOR */
|
|
(ch >= 0x206A && (ch <= 0x206F)) || /* [CONTROL CHARACTERS] */
|
|
(ch == 0xFEFF) || /* ZERO WIDTH NO-BREAK SPACE */
|
|
(ch >= 0xFFF9 && ch <= 0xFFFC) || /* [CONTROL CHARACTERS] */
|
|
(ch >= 0x1D173 && ch <= 0x1D17A)) { /* [MUSICAL CONTROL CHARACTERS] */
|
|
log_error("Invalid UTF-8: %lX (Non-ASCII control characters)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.3 Private use */
|
|
if ((ch >= 0xE000 && ch <= 0xF8FF) || /* [PRIVATE USE, PLANE 0] */
|
|
(ch >= 0xF0000 && ch <= 0xFFFFD) || /* [PRIVATE USE, PLANE 15] */
|
|
(ch >= 0x100000 && ch <= 0x10FFFD)) { /* [PRIVATE USE, PLANE 16] */
|
|
log_error("Invalid UTF-8: %lX (Private use)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.4 Non-character code points */
|
|
if ((ch >= 0xFDD0 && ch <= 0xFDEF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xFFFE && ch <= 0xFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x1FFFE && ch <= 0x1FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x2FFFE && ch <= 0x2FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x3FFFE && ch <= 0x3FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x4FFFE && ch <= 0x4FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x5FFFE && ch <= 0x5FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x6FFFE && ch <= 0x6FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x7FFFE && ch <= 0x7FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x8FFFE && ch <= 0x8FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x9FFFE && ch <= 0x9FFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xAFFFE && ch <= 0xAFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xBFFFE && ch <= 0xBFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xCFFFE && ch <= 0xCFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xDFFFE && ch <= 0xDFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xEFFFE && ch <= 0xEFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0xFFFFE && ch <= 0xFFFFF) || /* [NONCHARACTER CODE POINTS] */
|
|
(ch >= 0x10FFFE && ch <= 0x10FFFF)) { /* [NONCHARACTER CODE POINTS] */
|
|
log_error("Invalid UTF-8: %lX (Non-character code points)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.5 Surrogate codes */
|
|
if (ch >= 0xD800 && ch <= 0xDFFF) { /* [SURROGATE CODES] */
|
|
log_error("Invalid UTF-8: %lX (Surrogate codes)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.6 Inappropriate for plain text */
|
|
if ((ch == 0xFFF9) || /* INTERLINEAR ANNOTATION ANCHOR */
|
|
(ch == 0xFFFA) || /* INTERLINEAR ANNOTATION SEPARATOR */
|
|
(ch == 0xFFFB) || /* INTERLINEAR ANNOTATION TERMINATOR */
|
|
(ch == 0xFFFC) || /* OBJECT REPLACEMENT CHARACTER */
|
|
(ch == 0xFFFD)) { /* REPLACEMENT CHARACTER */
|
|
log_error("Invalid UTF-8: %lX (Inappropriate for plain text)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.7 Inappropriate for canonical representation */
|
|
if (ch >= 0x2FF0 && ch <= 0x2FFB) { /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */
|
|
log_error("Invalid UTF-8: %lX (Inappropriate for canonical representation)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.8 Change display properties or are deprecated */
|
|
switch (ch) {
|
|
case 0x0340: /* COMBINING GRAVE TONE MARK */
|
|
case 0x0341: /* COMBINING ACUTE TONE MARK */
|
|
case 0x200E: /* LEFT-TO-RIGHT MARK */
|
|
case 0x200F: /* RIGHT-TO-LEFT MARK */
|
|
case 0x202A: /* LEFT-TO-RIGHT EMBEDDING */
|
|
case 0x202B: /* RIGHT-TO-LEFT EMBEDDING */
|
|
case 0x202C: /* POP DIRECTIONAL FORMATTING */
|
|
case 0x202D: /* LEFT-TO-RIGHT OVERRIDE */
|
|
case 0x202E: /* RIGHT-TO-LEFT OVERRIDE */
|
|
case 0x206A: /* INHIBIT SYMMETRIC SWAPPING */
|
|
case 0x206B: /* ACTIVATE SYMMETRIC SWAPPING */
|
|
case 0x206C: /* INHIBIT ARABIC FORM SHAPING */
|
|
case 0x206D: /* ACTIVATE ARABIC FORM SHAPING */
|
|
case 0x206E: /* NATIONAL DIGIT SHAPES */
|
|
case 0x206F: /* NOMINAL DIGIT SHAPES */
|
|
log_error("Invalid UTF-8: %lX (Change display properties or are deprecated)", ch);
|
|
return false;
|
|
}
|
|
|
|
/* C.9 Tagging characters */
|
|
if ((ch == 0xE0001) || /* LANGUAGE TAG */
|
|
(ch >= 0xE0020 && ch <= 0xE007F)) { /* [TAGGING CHARACTERS] */
|
|
log_error("Invalid UTF-8: %lX (Tagging characters)", ch);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Checks whether the supplied name contains valid characters per RFC, returns true if so.
|
|
*
|
|
* It does NOT perform other formatting checks, e.g. that string starts with "iqn.",
|
|
* "naa." or "eui." ... along with subsequent formatting.
|
|
*/
|
|
static bool name_chars_are_valid(const char *name)
|
|
{
|
|
const unsigned char *bytes = (const unsigned char *)name;
|
|
|
|
if (!name)
|
|
return false;
|
|
|
|
/*
|
|
* rfc7143 4.2.7.1. iSCSI Name Properties
|
|
*
|
|
* Initiators and targets MUST support the receipt of iSCSI names of up
|
|
* to the maximum length of 223 bytes.
|
|
*/
|
|
if (strlen(name) > ISCSI_NAME_CHECK_LEN) {
|
|
log_error("Name is too long: %s (%zu)", name, strlen(name));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Iterate over each character in the string, validating that it is
|
|
* an accepable character. This is complicated somewhat as UTF-8
|
|
* encoding is supported.
|
|
*
|
|
* From rtf3629
|
|
* Char. number range | UTF-8 octet sequence
|
|
* (hexadecimal) | (binary)
|
|
* --------------------+---------------------------------------------
|
|
* 0000 0000-0000 007F | 0xxxxxxx
|
|
* 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
|
* 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
|
* 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
|
*/
|
|
while (*bytes) {
|
|
long code;
|
|
|
|
if ((bytes[0] & 0x80) == 0x00) {
|
|
/* Regular ASCII */
|
|
if (!valid_ascii_name_char(bytes[0]))
|
|
return false;
|
|
bytes += 1;
|
|
continue;
|
|
|
|
} else if ((bytes[0] & 0xe0) == 0xc0) {
|
|
/* 110xxxxx 10xxxxxx */
|
|
if ((bytes[1] & 0xc0) != 0x80) {
|
|
log_error("Invalid UTF-8: %02hhX %02hhX", bytes[0], bytes[1]);
|
|
return false;
|
|
}
|
|
code = ((long)(bytes[0] & 0x1f) << 6) |
|
|
((long)(bytes[1] & 0x3f) << 0);
|
|
bytes += 2;
|
|
} else if ((bytes[0] & 0xf0) == 0xe0) {
|
|
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
|
if (((bytes[1] & 0xc0) != 0x80) ||
|
|
((bytes[2] & 0xc0) != 0x80)) {
|
|
log_error("Invalid UTF-8: %02hhX %02hhX %02hhX",
|
|
bytes[0], bytes[1], bytes[2]);
|
|
return false;
|
|
}
|
|
code = ((long)(bytes[0] & 0x0f) << 12) |
|
|
((long)(bytes[1] & 0x3f) << 6) |
|
|
((long)(bytes[2] & 0x3f) << 0);
|
|
bytes += 3;
|
|
} else if ((bytes[0] & 0xf8) == 0xf0 && (bytes[0] <= 0xf4)) {
|
|
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
|
if (((bytes[1] & 0xc0) != 0x80) ||
|
|
((bytes[2] & 0xc0) != 0x80) ||
|
|
((bytes[3] & 0xc0) != 0x80)) {
|
|
log_error("Invalid UTF-8: %02hhX %02hhX %02hhX %02hhX",
|
|
bytes[0], bytes[1], bytes[2], bytes[3]);
|
|
return false;
|
|
}
|
|
code = ((long)(bytes[0] & 0x07) << 18) |
|
|
((long)(bytes[1] & 0x3f) << 12) |
|
|
((long)(bytes[2] & 0x3f) << 6) |
|
|
((long)(bytes[3] & 0x3f) << 0);
|
|
bytes += 4;
|
|
} else {
|
|
log_error("Invalid UTF-8: %02hhX", bytes[0]);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If we got here we have a valid UTF-8 character. Check it.
|
|
*/
|
|
if (!valid_utf8_name_char(code))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!name_chars_are_valid(name)) {
|
|
login_rsp_ini_err(conn, ISCSI_STATUS_INIT_ERR);
|
|
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;
|
|
}
|
|
|
|
if (logins_suspended) {
|
|
log_info("Initiator %s login rejected: service temporarily unavailable",
|
|
conn->initiator);
|
|
login_rsp_tgt_err(conn, ISCSI_STATUS_SVC_UNAVAILABLE);
|
|
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;
|
|
}
|
|
}
|
|
if (target->alias)
|
|
text_key_add(conn, "TargetAlias", target->alias);
|
|
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 bool on_internal_portal(struct connection *conn)
|
|
{
|
|
if (!internal_portal)
|
|
return false;
|
|
|
|
if (strchr(internal_portal, ' ')) {
|
|
char *portals = strdup(internal_portal);
|
|
|
|
if (portals) {
|
|
char *portal = strtok(portals, " ");
|
|
|
|
while (portal != NULL) {
|
|
if (!strcmp(portal, conn->target_portal)) {
|
|
free(portals);
|
|
return true;
|
|
}
|
|
portal = strtok(NULL, " ");
|
|
}
|
|
free(portals);
|
|
}
|
|
return false;
|
|
} else {
|
|
return !strcmp(internal_portal, conn->target_portal);
|
|
}
|
|
}
|
|
|
|
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) &&
|
|
!on_internal_portal(conn))
|
|
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;
|
|
}
|
|
}
|