Files
scst/iscsi-scst/usr/plain.c
Vladislav Bolkhovitin 20a2e4ae8d Copyrights updated
git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@408 d57e44dd-8a1f-0410-8b47-8ef2f437770f
2008-06-12 11:39:28 +00:00

636 lines
13 KiB
C

/*
* Plain file-based configuration file code.
*
* Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org>
* Copyright (C) 2007 - 2008 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2008 CMS Distribution Limited
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.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"
#define ACCT_CONFIG_FILE CONFIG_FILE
/*
* Account configuration code
*/
struct user {
struct qelem ulist;
u32 tid;
char *name;
char *password;
};
/* this is the orignal Ardis code. */
static char *target_sep_string(char **pp)
{
char *p = *pp;
char *q;
for (p = *pp; isspace(*p); p++)
;
for (q = p; *q && !isspace(*q); q++)
;
if (*q)
*q++ = 0;
else
p = NULL;
*pp = q;
return p;
}
static struct iscsi_key user_keys[] = {
{"IncomingUser",},
{"OutgoingUser",},
{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);
#define HASH_ORDER 4
#define acct_hash(x) ((x) & ((1 << HASH_ORDER) - 1))
static struct qelem trgt_acct_in[1 << HASH_ORDER];
static struct qelem trgt_acct_out[1 << HASH_ORDER];
static struct qelem *account_list_get(u32 tid, int dir)
{
struct qelem *list = NULL;
if (tid) {
list = (dir == AUTH_DIR_INCOMING) ?
&trgt_acct_in[acct_hash(tid)] : &trgt_acct_out[acct_hash(tid)];
} else
list = (dir == AUTH_DIR_INCOMING) ?
&discovery_users_in : &discovery_users_out;
return list;
}
static int plain_account_init(char *filename)
{
FILE *fp;
char buf[BUFSIZE], *p, *q;
u32 tid;
int idx, res = 0;
if (!(fp = fopen(filename, "r"))) {
return errno == ENOENT ? 0 : -errno;
}
tid = 0;
while (fgets(buf, sizeof(buf), fp)) {
q = buf;
p = target_sep_string(&q);
if (!p || *p == '#')
continue;
if (!strcasecmp(p, "Target")) {
tid = 0;
if (!(p = target_sep_string(&q)))
continue;
tid = target_find_by_name(p);
} else if (!((idx = param_index_by_name(p, user_keys)) < 0)) {
char *name, *pass;
name = target_sep_string(&q);
pass = target_sep_string(&q);
res = cops->account_add(tid, idx, name, pass);
if (res < 0) {
fprintf(stderr, "%s %s\n", name, pass);
break;
}
}
}
fclose(fp);
return res;
}
/* Return the first account if the length of name is zero */
static struct user *account_lookup_by_name(u32 tid, int dir, char *name)
{
struct qelem *list = account_list_get(tid, dir);
struct user *user = NULL;
list_for_each_entry(user, list, ulist) {
fprintf(stderr, "%u %s %s\n", user->tid, user->password, user->name);
if (user->tid != tid)
continue;
if (!strlen(name))
return user;
if (!strcmp(user->name, name))
return user;
}
return NULL;
}
static int plain_account_query(u32 tid, int dir, char *name, char *pass)
{
struct user *user;
if (!(user = account_lookup_by_name(tid, dir, name)))
return -ENOENT;
if (!strlen(name))
strncpy(name, user->name, ISCSI_NAME_LEN);
strncpy(pass, user->password, ISCSI_NAME_LEN);
return 0;
}
static void account_destroy(struct user *user)
{
if (!user)
return;
remque(&user->ulist);
free(user->name);
free(user->password);
free(user);
}
static int plain_account_del(u32 tid, int dir, char *name)
{
struct user *user;
if (!name || !(user = account_lookup_by_name(tid, dir, name)))
return -ENOENT;
account_destroy(user);
/* update the file here. */
return 0;
}
static struct user *account_create(void)
{
struct user *user;
if (!(user = malloc(sizeof(*user))))
return NULL;
memset(user, 0, sizeof(*user));
INIT_LIST_HEAD(&user->ulist);
return user;
}
static int plain_account_add(u32 tid, int dir, char *name, char *pass)
{
int err = -ENOMEM;
struct user *user;
struct qelem *list;
if (!name || !pass)
return -EINVAL;
if (tid) {
/* check here */
/* return -ENOENT; */
}
/* 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);
return -EINVAL;
}
if (!(user = account_create()) ||
!(user->name = strdup(name)) ||
!(user->password = strdup(pass)))
goto out;
user->tid = tid;
list = account_list_get(tid, dir);
if (dir == AUTH_DIR_OUTGOING && !list_empty(list)) {
struct user *old;
log_warning("Only one outgoing %s account is supported."
" Replacing the old one.\n",
tid ? "target" : "discovery");
old = (struct user *) list->q_forw;
account_destroy(old);
}
insque(user, list);
/* update the file here. */
return 0;
out:
account_destroy(user);
return err;
}
/*
* Access control code
*/
static int netmask_match_v6(struct sockaddr *sa1, struct sockaddr *sa2, uint32_t mbit)
{
uint16_t mask, a1[8], a2[8];
int i;
for (i = 0; i < 8; i++) {
a1[i] = ntohs(((struct sockaddr_in6 *) sa1)->sin6_addr.s6_addr16[i]);
a2[i] = ntohs(((struct sockaddr_in6 *) 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(struct sockaddr *sa1, struct sockaddr *sa2, uint32_t mbit)
{
uint32_t s1, s2, mask = ~((1 << (32 - mbit)) - 1);
s1 = htonl(((struct sockaddr_in *) sa1)->sin_addr.s_addr);
s2 = htonl(((struct sockaddr_in *) sa2)->sin_addr.s_addr);
if (~mask & s1)
return 0;
if (!((mask & s2) ^ (mask & s1)))
return 1;
return 0;
}
static int netmask_match(struct sockaddr *sa1, struct sockaddr *sa2, char *buf)
{
uint32_t mbit;
uint8_t family = sa1->sa_family;
mbit = strtoul(buf, NULL, 0);
if (mbit < 0 ||
(family == AF_INET && mbit > 31) ||
(family == AF_INET6 && mbit > 127))
return 0;
if (family == AF_INET)
return netmask_match_v4(sa1, sa2, mbit);
return netmask_match_v6(sa1, sa2, mbit);
}
static int address_match(struct sockaddr *sa1, struct sockaddr *sa2)
{
if (sa1->sa_family == AF_INET)
return ((struct sockaddr_in *) sa1)->sin_addr.s_addr ==
((struct sockaddr_in *) sa2)->sin_addr.s_addr;
else {
struct in6_addr *a1, *a2;
a1 = &((struct sockaddr_in6 *) sa1)->sin6_addr;
a2 = &((struct sockaddr_in6 *) sa2)->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)
{
struct sockaddr_storage from;
struct addrinfo hints, *res;
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, ","))) {
while (isspace(*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(res->ai_addr,
(struct sockaddr *) &from, q);
else
err = address_match(res->ai_addr,
(struct sockaddr *) &from);
freeaddrinfo(res);
if (err)
break;
}
return err;
}
static int initiator_match(u32 tid, int fd, 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 'reload feature'. It's slow, however, it 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';
if (!(p = strchr(buf, ' ')))
continue;
*(p++) = '\0';
if (target_find_by_name(buf) != tid && strcmp(buf, "ALL"))
continue;
err = __initiator_match(fd, p);
break;
}
fclose(fp);
return err;
}
static int plain_initiator_access(u32 tid, int fd)
{
if (initiator_match(tid, fd, "/etc/initiators.deny") &&
!initiator_match(tid, fd, "/etc/initiators.allow"))
return -EPERM;
else
return 0;
}
/*
* Main configuration code
*/
static int __plain_target_create(u32 *tid, char *name, int update)
{
int err;
struct iscsi_param params[session_key_last];
if (target_find_by_name(name)) {
log_error("duplicated target %s", name);
return -EINVAL;
}
if ((err = target_add(tid, name)) < 0)
return err;
param_set_defaults(params, session_keys);
if ((err = ki->param_set(*tid, 0, key_session, 0, params, 1)) < 0)
return err;
if (update)
; /* Update the config file here. */
return err;
}
static int plain_target_create(u32 *tid, char *name)
{
return __plain_target_create(tid, name, 1);
}
static int plain_target_destroy(u32 tid)
{
int err;
if ((err = target_del(tid)) < 0)
return err;
/* Update the config file here. */
return err;
}
static int __plain_param_set(u32 tid, u64 sid, int type,
u32 partial, struct iscsi_param *param, int update)
{
int err;
if ((err = ki->param_set(tid, sid, type, partial, param, 1)) < 0)
return err;
if (update)
;
return err;
}
static int plain_param_set(u32 tid, u64 sid, int type,
u32 partial, struct iscsi_param *param)
{
return __plain_param_set(tid, sid, type, partial, param, 1);
}
static int iscsi_param_partial_set(u32 tid, u64 sid, int type, int key, u32 val)
{
struct iscsi_param *param;
struct iscsi_param session_param[session_key_last];
struct iscsi_param target_param[target_key_last];
if (type == key_session)
param = session_param;
else
param = target_param;
param[key].local_val = val;
return __plain_param_set(tid, sid, type, 1 << key, param, 0);
}
static int plain_main_init(char *filename)
{
FILE *config;
char buf[BUFSIZE];
char *p, *q;
int idx;
u32 tid, val;
int res = 0;
if (!(config = fopen(filename, "r"))) {
return errno == ENOENT ? 0 : -errno;
}
tid = 0;
while (fgets(buf, BUFSIZE, config)) {
q = buf;
p = target_sep_string(&q);
if (!p || *p == '#')
continue;
if (!strcasecmp(p, "Target")) {
tid = 0;
if (!(p = target_sep_string(&q)))
continue;
if (__plain_target_create(&tid, p, 0))
log_debug(1, "creating target %s", p);
} else if (!strcasecmp(p, "Alias") && tid) {
;
} else if (!((idx = param_index_by_name(p, target_keys)) < 0) && tid) {
val = strtol(q, &q, 0);
if (param_check_val(target_keys, idx, &val) < 0) {
log_error("Wrong value %u for parameter %s\n",
val, target_keys[idx].name);
res = -1;
break;
}
iscsi_param_partial_set(tid, 0, key_target, idx, val);
} else if (!((idx = param_index_by_name(p, session_keys)) < 0) && tid) {
char *str = target_sep_string(&q);
if (param_str_to_val(session_keys, idx, str, &val) < 0) {
log_error("Wrong value %s for parameter %s\n",
str, session_keys[idx].name);
res = -1;
break;
}
if (param_check_val(session_keys, idx, &val) < 0) {
log_error("Wrong value %u for parameter %s\n",
val, session_keys[idx].name);
res = -1;
break;
}
iscsi_param_partial_set(tid, 0, key_session, idx, val);
} else if (param_index_by_name(p, user_keys) < 0) {
log_warning("Unknown iscsi-scstd.conf param: %s\n", p);
res = -1;
break;
}
}
fclose(config);
return res;
}
static int plain_default_load(char *params)
{
int i, err;
for (i = 0; i < 1 << HASH_ORDER; i++) {
INIT_LIST_HEAD(&trgt_acct_in[i]);
INIT_LIST_HEAD(&trgt_acct_out[i]);
}
/* First, we must finish the main configuration. */
if ((err = plain_main_init(params ? params : CONFIG_FILE)))
return err;
if ((err = plain_account_init(ACCT_CONFIG_FILE)) < 0)
return err;
/* TODO: error handling */
return err;
}
static int plain_init(char *params, char **isns, int *isns_ac)
{
FILE *config;
char buf[BUFSIZE];
char *p, *q;
if (!(config = fopen(params ? : CONFIG_FILE, "r")))
return -errno;
while (fgets(buf, BUFSIZE, config)) {
q = buf;
p = target_sep_string(&q);
if (!p || *p == '#')
continue;
if (!strcasecmp(p, "iSNSServer")) {
*isns = strdup(target_sep_string(&q));
} else if (!strcasecmp(p, "iSNSAccessControl")) {
char *str = target_sep_string(&q);
if (!strcasecmp(str, "Yes"))
*isns_ac = 1;
}
}
fclose(config);
return 0;
}
struct config_operations plain_ops = {
.init = plain_init,
.default_load = plain_default_load,
.target_add = plain_target_create,
.target_del = plain_target_destroy,
.param_set = plain_param_set,
.account_add = plain_account_add,
.account_del = plain_account_del,
.account_query = plain_account_query,
.initiator_access = plain_initiator_access,
};