mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 01:01:27 +00:00
1182 lines
24 KiB
C
1182 lines
24 KiB
C
/*
|
|
* Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org>
|
|
* 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.
|
|
*
|
|
* 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 <dirent.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/ip.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "iscsid.h"
|
|
|
|
#define BUFSIZE 4096
|
|
#define CONFIG_FILE "/etc/iscsi-scstd.conf"
|
|
|
|
/*
|
|
* Index must match ISCSI_USER_DIR_* and ISCSI_USER_DIR_OUTGOING must
|
|
* be the last to confirm expectations of __config_account_add()!!
|
|
*/
|
|
struct iscsi_key user_keys[] = {
|
|
{ .name = "IncomingUser",},
|
|
{ .name = "OutgoingUser",},
|
|
{ .name = NULL,},
|
|
};
|
|
|
|
static struct __qelem discovery_users_in = LIST_HEAD_INIT(discovery_users_in);
|
|
static struct __qelem discovery_users_out = LIST_HEAD_INIT(discovery_users_out);
|
|
|
|
struct iscsi_attr *iscsi_attr_lookup_by_sysfs_name(
|
|
struct __qelem *attrs_list, const char *sysfs_name)
|
|
{
|
|
struct iscsi_attr *attr;
|
|
|
|
list_for_each_entry(attr, attrs_list, ulist) {
|
|
if (!strcmp(attr->sysfs_name, sysfs_name))
|
|
return attr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct iscsi_attr *iscsi_attr_lookup_by_key(
|
|
struct __qelem *attrs_list, const char *key)
|
|
{
|
|
struct iscsi_attr *attr;
|
|
|
|
list_for_each_entry(attr, attrs_list, ulist) {
|
|
if (!strcmp(attr->attr_key, key))
|
|
return attr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void __iscsi_attr_destroy(struct iscsi_attr *attr, int del)
|
|
{
|
|
if (!attr)
|
|
return;
|
|
|
|
if (del)
|
|
list_del(&attr->ulist);
|
|
|
|
free((void *)attr->attr_key);
|
|
free((void *)attr->attr_value);
|
|
free(attr);
|
|
return;
|
|
}
|
|
|
|
void iscsi_attr_destroy(struct iscsi_attr *attr)
|
|
{
|
|
__iscsi_attr_destroy(attr, 1);
|
|
}
|
|
|
|
void iscsi_attrs_free(struct __qelem *attrs_list)
|
|
{
|
|
struct iscsi_attr *attr, *t;
|
|
|
|
list_for_each_entry_safe(attr, t, attrs_list, ulist) {
|
|
iscsi_attr_destroy(attr);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int iscsi_attr_create(int attr_size, struct __qelem *attrs_list,
|
|
const char *sysfs_name_tmpl, const char *key, const char *val,
|
|
u32 mode, struct iscsi_attr **res_attr)
|
|
{
|
|
int res = 0;
|
|
struct iscsi_attr *attr;
|
|
int num = 0;
|
|
|
|
if (iscsi_attr_lookup_by_key(attrs_list, key) != NULL) {
|
|
log_error("Attribute %s already exists", key);
|
|
res = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
attr = malloc(attr_size);
|
|
if (attr == NULL)
|
|
goto out;
|
|
|
|
memset(attr, 0, attr_size);
|
|
|
|
attr->attr_key = strdup(key);
|
|
if (attr->attr_key == NULL) {
|
|
log_error("Unable to duplicate attr_key %s", key);
|
|
res = -ENOMEM;
|
|
goto out_free;
|
|
}
|
|
|
|
if ((val != NULL) && (*val != '\0')) {
|
|
attr->attr_value = strdup(val);
|
|
if (attr->attr_value == NULL) {
|
|
log_error("Unable to duplicate attr_value %s", val);
|
|
res = -ENOMEM;
|
|
goto out_free;
|
|
}
|
|
}
|
|
|
|
attr->sysfs_mode = mode;
|
|
|
|
if (sysfs_name_tmpl == NULL)
|
|
goto add;
|
|
|
|
if (sysfs_name_tmpl != NULL) {
|
|
strlcpy(attr->sysfs_name, sysfs_name_tmpl, sizeof(attr->sysfs_name));
|
|
if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL)
|
|
goto add;
|
|
}
|
|
|
|
while (1) {
|
|
if (num == 0)
|
|
num++;
|
|
snprintf(attr->sysfs_name, sizeof(attr->sysfs_name),
|
|
"%s%d", sysfs_name_tmpl, num);
|
|
if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL)
|
|
break;
|
|
num++;
|
|
}
|
|
|
|
add:
|
|
list_add_tail(attr, attrs_list);
|
|
|
|
*res_attr = attr;
|
|
|
|
out:
|
|
return res;
|
|
|
|
out_free:
|
|
__iscsi_attr_destroy(attr, 0);
|
|
goto out;
|
|
}
|
|
|
|
int iscsi_attr_replace(struct __qelem *attrs_list, const char *sysfs_name,
|
|
char *raw_value)
|
|
{
|
|
int res = 0;
|
|
struct iscsi_attr *attr, *a;
|
|
char *key, *val, *new_key, *new_val;
|
|
|
|
key = config_sep_string(&raw_value);
|
|
val = config_sep_string(&raw_value);
|
|
|
|
attr = iscsi_attr_lookup_by_sysfs_name(attrs_list, sysfs_name);
|
|
if (attr == NULL) {
|
|
log_error("Unknown parameter %s\n", sysfs_name);
|
|
res = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
a = iscsi_attr_lookup_by_key(attrs_list, key);
|
|
|
|
if ((a != NULL) && (a != attr)) {
|
|
log_error("Attr %s (sysfs_name %s) already exists\n", key,
|
|
a->sysfs_name);
|
|
res = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
new_key = strdup(key);
|
|
if (new_key == NULL) {
|
|
log_error("Unable to duplicate attr_key %s", key);
|
|
res = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if ((val != NULL) && (*val != '\0')) {
|
|
new_val = strdup(val);
|
|
if (new_val == NULL) {
|
|
log_error("Unable to duplicate attr_value %s", val);
|
|
res = -ENOMEM;
|
|
free(new_key);
|
|
goto out;
|
|
}
|
|
} else
|
|
new_val = NULL;
|
|
|
|
free((void *)attr->attr_key);
|
|
attr->attr_key = new_key;
|
|
|
|
free((void *)attr->attr_value);
|
|
attr->attr_value = new_val;
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
static struct __qelem *account_list_get(struct target *target, int dir)
|
|
{
|
|
struct __qelem *list = NULL;
|
|
|
|
if (target != NULL) {
|
|
list = (dir == ISCSI_USER_DIR_INCOMING) ?
|
|
&target->target_in_accounts : &target->target_out_accounts;
|
|
} else
|
|
list = (dir == ISCSI_USER_DIR_INCOMING) ?
|
|
&discovery_users_in : &discovery_users_out;
|
|
|
|
return list;
|
|
}
|
|
|
|
char *config_sep_string(char **pp)
|
|
{
|
|
char *p;
|
|
char *q;
|
|
static char blank = '\0';
|
|
|
|
if ((pp == NULL) || (*pp == NULL))
|
|
return ␣
|
|
|
|
for (p = *pp; isspace(*p) || (*p == '='); p++)
|
|
;
|
|
|
|
for (q = p; (*q != '\0') && !isspace(*q) && (*q != '='); q++)
|
|
;
|
|
|
|
if (*q != '\0')
|
|
*q++ = '\0';
|
|
|
|
*pp = q;
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Strip leading and trailing whitespace.
|
|
*
|
|
* Modifies the contents of the parameter string.
|
|
*/
|
|
char *config_strip_string(char *s)
|
|
{
|
|
size_t size;
|
|
char *end;
|
|
|
|
size = strlen(s);
|
|
|
|
if (!size)
|
|
return s;
|
|
|
|
end = s + size - 1;
|
|
while (end >= s && isspace(*end))
|
|
end--;
|
|
*(end + 1) = '\0';
|
|
|
|
while (*s && isspace(*s))
|
|
s++;
|
|
|
|
return s;
|
|
}
|
|
|
|
static char *config_gets(char *buf, int size, const char *data, int *offset)
|
|
{
|
|
int offs = *offset, i = 0;
|
|
|
|
while ((i < size-1) && (data[offs] != '\n') && (data[offs] != ';') && (data[offs] != '\0'))
|
|
buf[i++] = data[offs++];
|
|
|
|
if ((i == 0) && (data[offs] == '\0'))
|
|
return NULL;
|
|
|
|
if (data[offs] != '\0')
|
|
offs++;
|
|
|
|
*offset = offs;
|
|
buf[i] = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
int accounts_empty(u32 tid, int dir)
|
|
{
|
|
struct target *target;
|
|
struct __qelem *list;
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL)
|
|
return 0;
|
|
} else
|
|
target = NULL;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
return list_empty(list);
|
|
}
|
|
|
|
static struct iscsi_attr *__account_lookup_by_name(struct target *target,
|
|
int dir, const char *name)
|
|
{
|
|
struct __qelem *list;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
return iscsi_attr_lookup_by_key(list, name);
|
|
}
|
|
|
|
static struct iscsi_attr *account_lookup_by_name(u32 tid, int dir, const char *name)
|
|
{
|
|
struct target *target;
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL)
|
|
return NULL;
|
|
} else
|
|
target = NULL;
|
|
|
|
return __account_lookup_by_name(target, dir, name);
|
|
}
|
|
|
|
struct iscsi_attr *account_get_first(u32 tid, int dir)
|
|
{
|
|
struct target *target;
|
|
struct __qelem *list;
|
|
struct iscsi_attr *user;
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL)
|
|
return NULL;
|
|
} else
|
|
target = NULL;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
list_for_each_entry(user, list, ulist) {
|
|
return user;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct iscsi_attr *account_lookup_by_sysfs_name(struct target *target,
|
|
int dir, const char *sysfs_name)
|
|
{
|
|
struct __qelem *list;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
return iscsi_attr_lookup_by_sysfs_name(list, sysfs_name);
|
|
}
|
|
|
|
int config_account_query(u32 tid, int dir, const char *name, char *pass)
|
|
{
|
|
struct iscsi_attr *user;
|
|
|
|
if (!(user = account_lookup_by_name(tid, dir, name)))
|
|
return -ENOENT;
|
|
|
|
strlcpy(pass, ISCSI_USER_PASS(user), ISCSI_NAME_LEN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int config_account_list(u32 tid, int dir, u32 *cnt, u32 *overflow,
|
|
char *buf, size_t buf_sz)
|
|
{
|
|
struct target *target;
|
|
struct __qelem *list;
|
|
struct iscsi_attr *user;
|
|
|
|
*cnt = *overflow = 0;
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL)
|
|
return -ENOENT;
|
|
} else
|
|
target = NULL;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
if (!list)
|
|
return -ENOENT;
|
|
|
|
list_for_each_entry(user, list, ulist) {
|
|
if (buf_sz >= ISCSI_NAME_LEN) {
|
|
strlcpy(buf, ISCSI_USER_NAME(user), ISCSI_NAME_LEN);
|
|
buf_sz -= ISCSI_NAME_LEN;
|
|
buf += ISCSI_NAME_LEN;
|
|
*cnt += 1;
|
|
} else
|
|
*overflow += 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void account_destroy(struct iscsi_attr *user)
|
|
{
|
|
iscsi_attr_destroy(user);
|
|
return;
|
|
}
|
|
|
|
void accounts_free(struct __qelem *accounts_list)
|
|
{
|
|
iscsi_attrs_free(accounts_list);
|
|
return;
|
|
}
|
|
|
|
int config_account_del(u32 tid, int dir, char *name, u32 cookie)
|
|
{
|
|
struct iscsi_attr *user;
|
|
int res = 0;
|
|
struct target *target;
|
|
|
|
if (!name) {
|
|
log_error("%s", "Name expected");
|
|
res = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL) {
|
|
log_error("Target %d not found", tid);
|
|
res = -ESRCH;
|
|
goto out;
|
|
}
|
|
} else
|
|
target = NULL;
|
|
|
|
user = __account_lookup_by_name(target, dir, name);
|
|
if (user == NULL) {
|
|
log_error("User %s not found", name);
|
|
res = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
log_debug(1, "Deleting %s user %s (%p, target %s, sysfs name %s)",
|
|
(dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming",
|
|
ISCSI_USER_NAME(user), user, target ? target->name : "discovery",
|
|
user->sysfs_name);
|
|
|
|
res = kernel_user_del(target, user, cookie);
|
|
if (res != 0)
|
|
goto out;
|
|
|
|
account_destroy(user);
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
static int account_create(struct __qelem *list, const char *sysfs_name,
|
|
const char *name, const char *pass, struct iscsi_attr **res_user)
|
|
{
|
|
return iscsi_attr_create(sizeof(struct iscsi_attr), list, sysfs_name,
|
|
name, pass, 0600, res_user);
|
|
}
|
|
|
|
int account_replace(struct target *target, int direction,
|
|
const char *sysfs_name, char *value)
|
|
{
|
|
struct __qelem *list;
|
|
|
|
list = account_list_get(target, direction);
|
|
|
|
return iscsi_attr_replace(list, sysfs_name, value);
|
|
}
|
|
|
|
int __config_account_add(struct target *target, int dir, char *name, char *pass,
|
|
const char *sysfs_name, int send_to_kern, u32 cookie)
|
|
{
|
|
int err = 0;
|
|
struct iscsi_attr *user;
|
|
struct __qelem *list;
|
|
|
|
if (!name || !pass || (*name == '\0') || (*pass == '\0')) {
|
|
log_error("%s", "Name or password is NULL");
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Check for minimum RFC defined value */
|
|
if (strlen(pass) < 12) {
|
|
log_error("Secret for user %s is too short. At least 12 bytes "
|
|
"are required\n", name);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (dir > ISCSI_USER_DIR_OUTGOING) {
|
|
log_error("Wrong direction %d\n", dir);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (sysfs_name == NULL)
|
|
sysfs_name = (dir == ISCSI_USER_DIR_OUTGOING) ?
|
|
user_keys[ISCSI_USER_DIR_OUTGOING].name :
|
|
user_keys[ISCSI_USER_DIR_INCOMING].name;
|
|
|
|
list = account_list_get(target, dir);
|
|
|
|
if (dir == ISCSI_USER_DIR_OUTGOING) {
|
|
struct iscsi_attr *old;
|
|
|
|
list_for_each_entry(old, list, ulist) {
|
|
log_warning("Only one outgoing %s account is "
|
|
"supported. Replacing the old one.\n",
|
|
target ? "target" : "discovery");
|
|
account_destroy(old);
|
|
break;
|
|
}
|
|
}
|
|
|
|
err = account_create(list, sysfs_name, name, pass, &user);
|
|
if (err != 0)
|
|
goto out;
|
|
|
|
if (send_to_kern && (sysfs_name != NULL)) {
|
|
err = kernel_user_add(target, user, cookie);
|
|
if (err != 0)
|
|
goto out_destroy;
|
|
}
|
|
|
|
log_debug(1, "User %s (%p, sysfs name %s) added to target %s "
|
|
"(direction %s)", ISCSI_USER_NAME(user), user, user->sysfs_name,
|
|
target ? target->name : "discovery",
|
|
(dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming");
|
|
|
|
out:
|
|
return err;
|
|
|
|
out_destroy:
|
|
account_destroy(user);
|
|
goto out;
|
|
}
|
|
|
|
int config_account_add(u32 tid, int dir, char *name, char *pass,
|
|
const char *sysfs_name, u32 cookie)
|
|
{
|
|
int err = 0;
|
|
struct target *target;
|
|
|
|
if (tid) {
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL) {
|
|
err = -ENOENT;
|
|
goto out;
|
|
}
|
|
} else
|
|
target = NULL;
|
|
|
|
err = __config_account_add(target, dir, name, pass, sysfs_name, 1, cookie);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Access control code
|
|
*/
|
|
|
|
typedef union {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sa_in;
|
|
struct sockaddr_in6 sa_in6;
|
|
} sockaddress;
|
|
|
|
static int netmask_match_v6(const struct sockaddr_in6 *sa1,
|
|
const struct sockaddr_in6 *sa2, uint32_t mbit)
|
|
{
|
|
uint16_t mask, a1[8], a2[8];
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
a1[i] = ntohs(sa1->sin6_addr.s6_addr16[i]);
|
|
a2[i] = ntohs(sa2->sin6_addr.s6_addr16[i]);
|
|
}
|
|
|
|
for (i = 0; i < mbit / 16; i++)
|
|
if (a1[i] ^ a2[i])
|
|
return 0;
|
|
|
|
if (mbit % 16) {
|
|
mask = ~((1 << (16 - (mbit % 16))) - 1);
|
|
if ((mask & a1[mbit / 16]) ^ (mask & a2[mbit / 16]))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int netmask_match_v4(const struct sockaddr_in *sa1,
|
|
const struct sockaddr_in *sa2,
|
|
uint32_t mbit)
|
|
{
|
|
uint32_t s1, s2, mask = ~((1 << (32 - mbit)) - 1);
|
|
|
|
s1 = htonl(sa1->sin_addr.s_addr);
|
|
s2 = htonl(sa2->sin_addr.s_addr);
|
|
|
|
if (~mask & s1)
|
|
return 0;
|
|
|
|
if (!((mask & s2) ^ (mask & s1)))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netmask_match(const sockaddress *sa1, const sockaddress *sa2,
|
|
char *buf)
|
|
{
|
|
unsigned long mbit;
|
|
uint8_t family = sa1->sa.sa_family;
|
|
|
|
mbit = strtoul(buf, NULL, 0);
|
|
if (mbit == ULONG_MAX ||
|
|
(family == AF_INET && mbit > 31) ||
|
|
(family == AF_INET6 && mbit > 127))
|
|
return 0;
|
|
|
|
if (family == AF_INET)
|
|
return netmask_match_v4(&sa1->sa_in, &sa2->sa_in, mbit);
|
|
|
|
return netmask_match_v6(&sa1->sa_in6, &sa2->sa_in6, mbit);
|
|
}
|
|
|
|
static int address_match(const sockaddress *sa1, const sockaddress *sa2)
|
|
{
|
|
if (sa1->sa.sa_family == AF_INET)
|
|
return sa1->sa_in.sin_addr.s_addr ==
|
|
sa2->sa_in.sin_addr.s_addr;
|
|
else {
|
|
const struct in6_addr *a1, *a2;
|
|
|
|
a1 = &sa1->sa_in6.sin6_addr;
|
|
a2 = &sa2->sa_in6.sin6_addr;
|
|
|
|
return (a1->s6_addr32[0] == a2->s6_addr32[0] &&
|
|
a1->s6_addr32[1] == a2->s6_addr32[1] &&
|
|
a1->s6_addr32[2] == a2->s6_addr32[2] &&
|
|
a1->s6_addr32[3] == a2->s6_addr32[3]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __initiator_match(int fd, char *str)
|
|
{
|
|
sockaddress from;
|
|
socklen_t len;
|
|
char *p, *q;
|
|
int err = 0;
|
|
|
|
len = sizeof(from);
|
|
if (getpeername(fd, (struct sockaddr *) &from, &len) < 0)
|
|
return 0;
|
|
|
|
while ((p = strsep(&str, ","))) {
|
|
struct addrinfo hints, *res;
|
|
|
|
while (isblank(*p))
|
|
p++;
|
|
|
|
if (!strcmp(p, "ALL"))
|
|
return 1;
|
|
|
|
if (*p == '[') {
|
|
p++;
|
|
if (!(q = strchr(p, ']')))
|
|
return 0;
|
|
*(q++) = '\0';
|
|
} else
|
|
q = p;
|
|
|
|
if ((q = strchr(q, '/')))
|
|
*(q++) = '\0';
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
|
|
if (getaddrinfo(p, NULL, &hints, &res) < 0)
|
|
return 0;
|
|
|
|
if (q)
|
|
err = netmask_match((sockaddress *)res->ai_addr, &from, q);
|
|
else
|
|
err = address_match((sockaddress *)res->ai_addr, &from);
|
|
|
|
freeaddrinfo(res);
|
|
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int initiator_match(u32 tid, int fd, const char *filename)
|
|
{
|
|
int err = 0;
|
|
FILE *fp;
|
|
char buf[BUFSIZE], *p;
|
|
|
|
if (!(fp = fopen(filename, "r")))
|
|
return err;
|
|
|
|
/*
|
|
* Every time we are called, we read the file. So we don't need to
|
|
* implement the 'reload feature'. It's slow, but that doesn't matter.
|
|
*/
|
|
while ((p = fgets(buf, sizeof(buf), fp))) {
|
|
if (!p || *p == '#')
|
|
continue;
|
|
|
|
p = &buf[strlen(buf) - 1];
|
|
if (*p != '\n')
|
|
continue;
|
|
*p = '\0';
|
|
|
|
p = buf;
|
|
while (!isblank(*p) && (*p != '\0'))
|
|
p++;
|
|
if (*p == '\0')
|
|
continue;
|
|
|
|
*p = '\0';
|
|
p++;
|
|
|
|
if (target_find_id_by_name(buf) != tid && strcmp(buf, "ALL"))
|
|
continue;
|
|
|
|
err = __initiator_match(fd, p);
|
|
break;
|
|
}
|
|
|
|
fclose(fp);
|
|
return err;
|
|
}
|
|
|
|
int config_initiator_access_allowed(u32 tid, int fd)
|
|
{
|
|
if (initiator_match(tid, fd, "/etc/initiators.deny") &&
|
|
!initiator_match(tid, fd, "/etc/initiators.allow"))
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Main configuration code
|
|
*/
|
|
|
|
int config_target_create(u32 *tid, char *name)
|
|
{
|
|
int err;
|
|
struct target *target;
|
|
|
|
err = target_create(name, &target);
|
|
if (err != 0)
|
|
goto out;
|
|
|
|
err = target_add(target, tid, 0);
|
|
if (err != 0)
|
|
goto out_free;
|
|
|
|
out:
|
|
return err;
|
|
|
|
out_free:
|
|
target_free(target);
|
|
goto out;
|
|
}
|
|
|
|
int config_target_destroy(u32 tid)
|
|
{
|
|
int err;
|
|
|
|
if ((err = target_del(tid, 0)) < 0)
|
|
return err;
|
|
|
|
return err;
|
|
}
|
|
|
|
int config_params_get(u32 tid, u64 sid, int type, struct iscsi_param *params)
|
|
{
|
|
int err, i;
|
|
struct target *target;
|
|
|
|
if (sid != 0) {
|
|
err = kernel_params_get(tid, sid, type, params);
|
|
goto out;
|
|
}
|
|
|
|
err = 0;
|
|
|
|
target = target_find_by_id(tid);
|
|
if (target == NULL) {
|
|
log_error("target %d not found", tid);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (type == key_session) {
|
|
for (i = 0; i < session_key_last; i++)
|
|
params[i].val = target->session_params[i];
|
|
} else {
|
|
for (i = 0; i < target_key_last; i++)
|
|
params[i].val = target->target_params[i];
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
int config_params_set(u32 tid, u64 sid, int type, u32 partial,
|
|
struct iscsi_param *params)
|
|
{
|
|
int err, i;
|
|
struct target *target;
|
|
|
|
if (sid != 0) {
|
|
err = kernel_params_set(tid, sid, type, partial, params);
|
|
goto out;
|
|
}
|
|
|
|
err = 0;
|
|
|
|
target = target_find_by_id(tid);
|
|
if (target == 0) {
|
|
log_error("target %d not found", tid);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (partial == 0)
|
|
partial = (typeof(partial))-1;
|
|
|
|
if (type == key_session) {
|
|
for (i = 0; i < session_key_last; i++) {
|
|
uint32_t in_val = params[i].val;
|
|
if (partial & (1 << i)) {
|
|
err = params_check_val(session_keys, i, ¶ms[i].val);
|
|
if (err < 0) {
|
|
log_error("%s: Wrong value %u->%u for session parameter %s\n",
|
|
__func__, in_val, params[i].val, session_keys[i].name);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i < session_key_last; i++) {
|
|
if (partial & (1 << i))
|
|
target->session_params[i] = params[i].val;
|
|
}
|
|
} else {
|
|
for (i = 0; i < target_key_last; i++) {
|
|
uint32_t in_val = params[i].val;
|
|
if (partial & (1 << i)) {
|
|
err = params_check_val(target_keys, i, ¶ms[i].val);
|
|
if (err < 0) {
|
|
log_error("%s: Wrong value %u->%u for target parameter %s\n",
|
|
__func__, in_val, params[i].val, target_keys[i].name);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i < target_key_last; i++) {
|
|
if (partial & (1 << i))
|
|
target->target_params[i] = params[i].val;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
int config_parse_main(const char *data, u32 cookie)
|
|
{
|
|
char buf[BUFSIZE];
|
|
char *p, *q, *n;
|
|
int idx, offset = 0;
|
|
u32 val;
|
|
int res = 0;
|
|
struct target *target = NULL;
|
|
int global_section = 1; /* supposed to be bool and true */
|
|
int parsed_something = 0; /* supposed to be bool and false */
|
|
int stop_on_errors = (cookie != 0);
|
|
|
|
while (config_gets(buf, sizeof(buf), data, &offset)) {
|
|
parsed_something = 1;
|
|
/*
|
|
* If stop_on_errors is false, let's always continue parsing
|
|
* and only report errors.
|
|
*/
|
|
if (stop_on_errors && (res != 0))
|
|
goto out_target_free;
|
|
|
|
q = buf;
|
|
p = config_sep_string(&q);
|
|
if ((*p == '#') || (*p == '\0'))
|
|
continue;
|
|
|
|
if (!strcasecmp(p, "Target")) {
|
|
global_section = 0;
|
|
|
|
if (target != NULL) {
|
|
res = target_add(target, NULL, cookie);
|
|
if (res != 0)
|
|
target_free(target);
|
|
}
|
|
|
|
target = NULL;
|
|
p = config_sep_string(&q);
|
|
if (*p == '\0') {
|
|
log_error("Target name required on %s\n", q);
|
|
continue;
|
|
}
|
|
|
|
n = config_sep_string(&q);
|
|
if (*n != '\0') {
|
|
log_error("Unexpected parameter value %s\n", n);
|
|
res = -EINVAL;
|
|
continue;
|
|
}
|
|
|
|
log_debug(1, "Creating target %s", p);
|
|
res = target_create(p, &target);
|
|
if (res != 0)
|
|
goto out;
|
|
} else if (!strcasecmp(p, "Alias") && target) {
|
|
;
|
|
} else if (!((idx = params_index_by_name(p, target_keys)) < 0) && (target != NULL)) {
|
|
char *str = config_sep_string(&q);
|
|
|
|
n = config_sep_string(&q);
|
|
if (*n != '\0') {
|
|
log_error("Unexpected parameter value %s\n", n);
|
|
res = -EINVAL;
|
|
continue;
|
|
}
|
|
|
|
res = params_str_to_val(target_keys, idx, str, &val);
|
|
if (res < 0) {
|
|
log_error("Wrong value %s for parameter %s\n",
|
|
str, target_keys[idx].name);
|
|
continue;
|
|
}
|
|
|
|
uint32_t in_val = val;
|
|
res = params_check_val(target_keys, idx, &val);
|
|
if (res < 0) {
|
|
log_error("%s: Wrong value %u->%u for target parameter %s\n",
|
|
__func__, in_val, val, target_keys[idx].name);
|
|
continue;
|
|
}
|
|
target->target_params[idx] = val;
|
|
} else if (!((idx = params_index_by_name(p, session_keys)) < 0) && (target != NULL)) {
|
|
char *str = config_sep_string(&q);
|
|
|
|
n = config_sep_string(&q);
|
|
if (*n != '\0') {
|
|
log_error("Unexpected parameter value %s\n", n);
|
|
res = -EINVAL;
|
|
continue;
|
|
}
|
|
|
|
res = params_str_to_val(session_keys, idx, str, &val);
|
|
if (res < 0) {
|
|
log_error("Wrong value %s for parameter %s\n",
|
|
str, session_keys[idx].name);
|
|
continue;
|
|
}
|
|
|
|
uint32_t in_val = val;
|
|
res = params_check_val(session_keys, idx, &val);
|
|
if (res < 0) {
|
|
log_error("%s: Wrong value %u->%u for session parameter %s\n",
|
|
__func__, in_val, val, session_keys[idx].name);
|
|
continue;
|
|
}
|
|
target->session_params[idx] = val;
|
|
} else if (!((idx = params_index_by_name_numwild(p, user_keys)) < 0) &&
|
|
((target != NULL) || global_section)) {
|
|
char *name, *pass;
|
|
|
|
name = config_sep_string(&q);
|
|
pass = config_sep_string(&q);
|
|
|
|
n = config_sep_string(&q);
|
|
if (*n != '\0') {
|
|
log_error("Unexpected parameter value %s\n", n);
|
|
res = -EINVAL;
|
|
continue;
|
|
}
|
|
|
|
res = __config_account_add(target, idx, name, pass, p,
|
|
(target == 0), 0);
|
|
if (res < 0)
|
|
continue;
|
|
} else if (global_section &&
|
|
(!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME) ||
|
|
!strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME)))
|
|
continue;
|
|
else {
|
|
log_error("Unknown or unexpected param: %s\n", p);
|
|
res = -EINVAL;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (stop_on_errors && (res != 0))
|
|
goto out_target_free;
|
|
|
|
if (target != NULL) {
|
|
res = target_add(target, NULL, cookie);
|
|
if (res != 0)
|
|
goto out_target_free;
|
|
}
|
|
|
|
out:
|
|
if (stop_on_errors) {
|
|
if ((res == 0) && !parsed_something)
|
|
res = -ENOENT;
|
|
} else
|
|
res = 0;
|
|
|
|
return res;
|
|
|
|
out_target_free:
|
|
if (target != NULL)
|
|
target_free(target);
|
|
goto out;
|
|
}
|
|
|
|
static int config_isns_load(const char *config)
|
|
{
|
|
char buf[BUFSIZE];
|
|
int offset = 0;
|
|
char *p, *q;
|
|
|
|
while (config_gets(buf, sizeof(buf), config, &offset)) {
|
|
q = buf;
|
|
p = config_sep_string(&q);
|
|
if ((*p == '\0') || (*p == '#'))
|
|
continue;
|
|
if (!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME)) {
|
|
free(isns_server);
|
|
isns_server = strdup(config_sep_string(&q));
|
|
} else if (!strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME)) {
|
|
char *str = config_sep_string(&q);
|
|
|
|
if (!strcasecmp(str, "No"))
|
|
isns_access_control = 0;
|
|
else
|
|
isns_access_control = 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int config_load(const char *config_name)
|
|
{
|
|
int i, err = 0, rc;
|
|
int config;
|
|
const char *cname;
|
|
int size;
|
|
char *buf;
|
|
|
|
if (config_name != NULL)
|
|
cname = config_name;
|
|
else
|
|
cname = CONFIG_FILE;
|
|
|
|
config = open(cname, O_RDONLY);
|
|
if (config == -1) {
|
|
if ((errno == ENOENT) && (config_name == NULL)) {
|
|
goto out;
|
|
} else {
|
|
err = -errno;
|
|
log_error("Open config file %s failed: %s", cname,
|
|
strerror(errno));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
size = lseek(config, 0, SEEK_END);
|
|
if (size < 0) {
|
|
err = -errno;
|
|
log_error("lseek() failed: %s", strerror(errno));
|
|
goto out_close;
|
|
}
|
|
|
|
buf = malloc(size+1);
|
|
if (buf == NULL) {
|
|
err = -ENOMEM;
|
|
log_error("malloc() failed: %s", strerror(-err));
|
|
goto out_close;
|
|
}
|
|
|
|
rc = lseek(config, 0, SEEK_SET);
|
|
if (rc < 0) {
|
|
err = -errno;
|
|
log_error("lseek() failed: %s", strerror(errno));
|
|
goto out_free;
|
|
}
|
|
|
|
i = 0;
|
|
do {
|
|
rc = read(config, &buf[i], size - i);
|
|
if (rc < 0) {
|
|
err = -errno;
|
|
log_error("read() failed: %s", strerror(errno));
|
|
goto out_free;
|
|
} else if (rc == 0)
|
|
break;
|
|
i += rc;
|
|
} while (i < size);
|
|
|
|
size = i;
|
|
buf[size] = '\0';
|
|
|
|
err = config_isns_load(buf);
|
|
if ((err == 0) && (isns_server != NULL)) {
|
|
rc = isns_init();
|
|
|
|
if (rc != 0) {
|
|
log_error("iSNS server %s init failed: %d", isns_server, rc);
|
|
isns_exit();
|
|
}
|
|
}
|
|
|
|
config_parse_main(buf, 0);
|
|
|
|
out_free:
|
|
free(buf);
|
|
|
|
out_close:
|
|
close(config);
|
|
|
|
out:
|
|
return err;
|
|
}
|