mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
This patch only affects code formatting and does not change any functionality. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@8356 d57e44dd-8a1f-0410-8b47-8ef2f437770f
506 lines
12 KiB
C
506 lines
12 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 <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/ip.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <ifaddrs.h>
|
|
#include <unistd.h>
|
|
|
|
#include "iscsid.h"
|
|
|
|
struct __qelem targets_list = LIST_HEAD_INIT(targets_list);
|
|
|
|
const char *iscsi_make_full_initiator_name(int per_portal_acl,
|
|
const char *initiator_name, const char *target_portal,
|
|
char *buf, int size)
|
|
{
|
|
if (per_portal_acl)
|
|
snprintf(buf, size, "%s#%s", initiator_name,
|
|
target_portal);
|
|
else
|
|
snprintf(buf, size, "%s", initiator_name);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Written by Jack Handy - jakkhandy@hotmail.com
|
|
* Taken by Gennadiy Nerubayev <parakie@gmail.com> from
|
|
* http://www.codeproject.com/KB/string/wildcmp.aspx. No license attached
|
|
* to it, and it's posted on a free site; assumed to be free for use.
|
|
*
|
|
* Added the negative sign support - VLNB
|
|
*
|
|
* Also see comment for wildcmp().
|
|
*
|
|
* SCST core also has a copy of this code, so fixing a bug here, don't forget
|
|
* to fix the copy too!
|
|
*/
|
|
static int __wildcmp(const char *wild, const char *string, int recursion_level)
|
|
{
|
|
const char *cp = NULL, *mp = NULL;
|
|
|
|
while ((*string) && (*wild != '*')) {
|
|
if ((*wild == '!') && (recursion_level == 0))
|
|
return !__wildcmp(++wild, string, ++recursion_level);
|
|
|
|
if ((tolower(*wild) != tolower(*string)) && (*wild != '?'))
|
|
return 0;
|
|
|
|
wild++;
|
|
string++;
|
|
}
|
|
|
|
while (*string) {
|
|
if ((*wild == '!') && (recursion_level == 0))
|
|
return !__wildcmp(++wild, string, ++recursion_level);
|
|
|
|
if (*wild == '*') {
|
|
if (!*++wild)
|
|
return 1;
|
|
|
|
mp = wild;
|
|
cp = string+1;
|
|
} else if ((tolower(*wild) == tolower(*string)) || (*wild == '?')) {
|
|
wild++;
|
|
string++;
|
|
} else {
|
|
wild = mp;
|
|
string = cp++;
|
|
}
|
|
}
|
|
|
|
while (*wild == '*')
|
|
wild++;
|
|
|
|
return !*wild;
|
|
}
|
|
|
|
/*
|
|
* Returns true if string "string" matches pattern "wild", false otherwise.
|
|
* Pattern is a regular DOS-type pattern, containing '*' and '?' symbols.
|
|
* '*' means match all any symbols, '?' means match only any single symbol.
|
|
*
|
|
* For instance:
|
|
* if (wildcmp("bl?h.*", "blah.jpg")) {
|
|
* // match
|
|
* } else {
|
|
* // no match
|
|
* }
|
|
*
|
|
* Also it supports boolean inversion sign '!', which does boolean inversion of
|
|
* the value of the rest of the string. Only one '!' allowed in the pattern,
|
|
* other '!' are treated as regular symbols. For instance:
|
|
* if (wildcmp("bl!?h.*", "blah.jpg")) {
|
|
* // no match
|
|
* } else {
|
|
* // match
|
|
* }
|
|
*
|
|
* Also see comment for __wildcmp().
|
|
*/
|
|
static int wildcmp(const char *wild, const char *string)
|
|
{
|
|
return __wildcmp(wild, string, 0);
|
|
}
|
|
|
|
/*
|
|
* Evaluate the list with wildcard patterns as follows:
|
|
* - If only positive wildcard patterns have been specified, it is sufficient
|
|
* that one wildcard pattern matches (logical or).
|
|
* - If only negative wildcard patterns have been specified, none of these
|
|
* patterns must match (logical and).
|
|
* - If positive and negative wildcard patterns have been specified, one of
|
|
* the positive patterns must match and none of the negative.
|
|
*/
|
|
int target_portal_allowed(struct target *target,
|
|
const char *target_portal, const char *initiator_name)
|
|
{
|
|
int res;
|
|
char full_initiator_name[ISCSI_FULL_NAME_LEN];
|
|
|
|
if (!list_empty(&target->allowed_portals)) {
|
|
struct iscsi_attr *attr;
|
|
bool any_pos_cond = false, pos_match = false, neg_match = true;
|
|
|
|
list_for_each_entry(attr, &target->allowed_portals, ulist) {
|
|
bool match = wildcmp(attr->attr_key, target_portal);
|
|
|
|
if (attr->attr_key[0] != '!') {
|
|
any_pos_cond = true;
|
|
pos_match = pos_match || match;
|
|
} else {
|
|
neg_match = neg_match && match;
|
|
}
|
|
}
|
|
res = (!any_pos_cond || pos_match) && neg_match;
|
|
if (res == 0)
|
|
goto out;
|
|
}
|
|
|
|
res = kernel_initiator_allowed(target->tid,
|
|
iscsi_make_full_initiator_name(target->per_portal_acl,
|
|
initiator_name, target_portal,
|
|
full_initiator_name, sizeof(full_initiator_name)));
|
|
if (res < 0)
|
|
res = 0; /* false */
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
static int is_addr_loopback(char *addr)
|
|
{
|
|
struct in_addr ia;
|
|
struct in6_addr ia6;
|
|
|
|
if (inet_pton(AF_INET, addr, &ia) == 1)
|
|
return !strncmp(addr, "127.", 4);
|
|
|
|
if (inet_pton(AF_INET6, addr, &ia6) == 1)
|
|
return IN6_IS_ADDR_LOOPBACK(&ia6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int is_addr_unspecified(char *addr)
|
|
{
|
|
struct in_addr ia;
|
|
struct in6_addr ia6;
|
|
|
|
if (inet_pton(AF_INET, addr, &ia) == 1)
|
|
return (ia.s_addr == 0);
|
|
|
|
if (inet_pton(AF_INET6, addr, &ia6) == 1)
|
|
return IN6_IS_ADDR_UNSPECIFIED(&ia6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void target_print_addr(struct connection *conn, char *addr, int family)
|
|
{
|
|
char taddr[NI_MAXHOST + NI_MAXSERV + 5];
|
|
|
|
snprintf(taddr, sizeof(taddr),
|
|
(family == AF_INET) ? "%s:%d,1" : "[%s]:%d,1",
|
|
addr, server_port);
|
|
|
|
text_key_add(conn, "TargetAddress", taddr);
|
|
}
|
|
|
|
static void target_list_build_ifaddrs(struct connection *conn,
|
|
struct target *target, char *exclude_addr, int family)
|
|
{
|
|
struct ifaddrs *ifaddr, *ifa;
|
|
char if_addr[NI_MAXHOST];
|
|
|
|
getifaddrs(&ifaddr);
|
|
|
|
for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
|
|
if (!ifa->ifa_addr)
|
|
continue;
|
|
|
|
int sa_family = ifa->ifa_addr->sa_family;
|
|
|
|
if (sa_family == family) {
|
|
if (getnameinfo(ifa->ifa_addr, (family == AF_INET) ?
|
|
sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6),
|
|
if_addr, sizeof(if_addr),
|
|
NULL, 0, NI_NUMERICHOST))
|
|
continue;
|
|
|
|
if (strcmp(exclude_addr, if_addr) &&
|
|
!is_addr_loopback(if_addr) &&
|
|
target_portal_allowed(target, if_addr, conn->initiator))
|
|
target_print_addr(conn, if_addr, family);
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifaddr);
|
|
return;
|
|
}
|
|
|
|
void target_list_build(struct connection *conn, char *target_name)
|
|
{
|
|
struct target *target;
|
|
struct sockaddr_storage ss1, ss2;
|
|
socklen_t slen = sizeof(struct sockaddr_storage);
|
|
char portal[NI_MAXHOST];
|
|
int family, i;
|
|
|
|
if (conn->getsockname(conn->fd, (struct sockaddr *) &ss1, &slen)) {
|
|
log_error("getsockname failed: %m");
|
|
return;
|
|
}
|
|
|
|
family = ss1.ss_family;
|
|
|
|
list_for_each_entry(target, &targets_list, tlist) {
|
|
if (target_name && strcmp(target->name, target_name))
|
|
continue;
|
|
|
|
if (!target->tgt_enabled ||
|
|
!isns_scn_access_allowed(target->tid, conn->initiator) ||
|
|
!config_initiator_access_allowed(target->tid, conn->fd) ||
|
|
!target_portal_allowed(target, conn->target_portal, conn->initiator))
|
|
continue;
|
|
|
|
text_key_add(conn, "TargetName", target->name);
|
|
target_print_addr(conn, conn->target_portal, family);
|
|
|
|
for (i = 0; i < LISTEN_MAX && poll_array[i].fd; i++) {
|
|
slen = sizeof(struct sockaddr_storage);
|
|
|
|
if (getsockname(poll_array[i].fd,
|
|
(struct sockaddr *) &ss2, &slen))
|
|
continue;
|
|
|
|
if (getnameinfo((struct sockaddr *) &ss2, slen, portal,
|
|
sizeof(portal), NULL, 0, NI_NUMERICHOST))
|
|
continue;
|
|
|
|
if (ss2.ss_family != family)
|
|
continue;
|
|
|
|
if (is_addr_unspecified(portal))
|
|
target_list_build_ifaddrs(conn, target,
|
|
conn->target_portal, family);
|
|
else if (strcmp(conn->target_portal, portal) &&
|
|
!is_addr_loopback(portal) &&
|
|
target_portal_allowed(target, portal,
|
|
conn->initiator))
|
|
target_print_addr(conn, portal, family);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
u32 target_find_id_by_name(const char *name)
|
|
{
|
|
struct target *target;
|
|
|
|
list_for_each_entry(target, &targets_list, tlist) {
|
|
if (!strcasecmp(target->name, name))
|
|
return target->tid;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct target *target_find_by_name(const char *name)
|
|
{
|
|
struct target *target;
|
|
|
|
list_for_each_entry(target, &targets_list, tlist) {
|
|
if (!strcasecmp(target->name, name))
|
|
return target;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct target *target_find_by_id(u32 tid)
|
|
{
|
|
struct target *target;
|
|
|
|
list_for_each_entry(target, &targets_list, tlist) {
|
|
if (target->tid == tid)
|
|
return target;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int target_del(u32 tid, u32 cookie)
|
|
{
|
|
struct target *target;
|
|
int err;
|
|
|
|
err = kernel_target_destroy(tid, cookie);
|
|
|
|
if (err < 0 && err != -ENOENT)
|
|
return err;
|
|
|
|
target = target_find_by_id(tid);
|
|
if (!err && !target)
|
|
/* A leftover kernel object was cleaned up - don't complain. */
|
|
return 0;
|
|
|
|
if (!target)
|
|
return -ENOENT;
|
|
|
|
while (1) {
|
|
/* We might need to handle session(s) removal event(s) from the kernel */
|
|
while (handle_iscsi_events(nl_fd, false) == 0)
|
|
;
|
|
|
|
/* Someone else may have already freed the target object by now. */
|
|
target = target_find_by_id(tid);
|
|
if (!target) {
|
|
log_info("%s: the target with tid = %u was already freed", __func__, tid);
|
|
return 0;
|
|
}
|
|
|
|
if (list_empty(&target->sessions_list))
|
|
break;
|
|
|
|
/* We have not yet received session(s) removal event(s), so keep waiting */
|
|
log_debug(1, "Target %d has sessions, keep waiting", tid);
|
|
usleep(50000);
|
|
}
|
|
|
|
/*
|
|
* Remove target from the list after waiting for all sessions
|
|
* deleted, because we are looking for this target in list during
|
|
* each session delete.
|
|
*/
|
|
list_del(&target->tlist);
|
|
|
|
if (target->tgt_enabled)
|
|
isns_target_deregister(target->name);
|
|
|
|
target_free(target);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void target_free(struct target *target)
|
|
{
|
|
accounts_free(&target->target_in_accounts);
|
|
accounts_free(&target->target_out_accounts);
|
|
|
|
iscsi_attrs_free(&target->allowed_portals);
|
|
|
|
free(target);
|
|
return;
|
|
}
|
|
|
|
int target_create(const char *name, struct target **out_target)
|
|
{
|
|
int res = 0;
|
|
struct target *target;
|
|
|
|
if (name == NULL) {
|
|
res = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
target = malloc(sizeof(*target));
|
|
if (target == NULL) {
|
|
res = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
memset(target, 0, sizeof(*target));
|
|
strlcpy(target->name, name, sizeof(target->name));
|
|
|
|
params_set_defaults(target->target_params, target_keys);
|
|
params_set_defaults(target->session_params, session_keys);
|
|
|
|
INIT_LIST_HEAD(&target->tlist);
|
|
INIT_LIST_HEAD(&target->sessions_list);
|
|
INIT_LIST_HEAD(&target->target_in_accounts);
|
|
INIT_LIST_HEAD(&target->target_out_accounts);
|
|
INIT_LIST_HEAD(&target->allowed_portals);
|
|
INIT_LIST_HEAD(&target->isns_head);
|
|
|
|
*out_target = target;
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
int target_add(struct target *target, u32 *tid, u32 cookie)
|
|
{
|
|
int err;
|
|
|
|
if (target_find_by_name(target->name)) {
|
|
log_error("duplicated target %s", target->name);
|
|
err = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
err = kernel_target_create(target, tid, cookie);
|
|
if (err != 0)
|
|
goto out;
|
|
|
|
list_add_tail(&target->tlist, &targets_list);
|
|
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
bool target_redirected(struct target *target, struct connection *conn)
|
|
{
|
|
bool res = false, rc;
|
|
union {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
} sa;
|
|
socklen_t slen = sizeof(sa);
|
|
char tmp[NI_MAXHOST + 1];
|
|
char addr[NI_MAXHOST + 3];
|
|
char redirect[NI_MAXHOST + NI_MAXSERV + 4];
|
|
char *p;
|
|
|
|
if (strlen(target->redirect.addr) == 0)
|
|
goto out;
|
|
|
|
rc = getsockname(conn->fd, (struct sockaddr *)&sa.sa, &slen);
|
|
if (rc != 0) {
|
|
log_error("getsockname() failed: %s", strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
rc = getnameinfo(&sa.sa, sizeof(sa), tmp, sizeof(tmp), NULL, 0, NI_NUMERICHOST);
|
|
if (rc != 0) {
|
|
log_error("getnameinfo() failed: %s", get_error_str(rc));
|
|
goto out;
|
|
}
|
|
|
|
if ((p = strrchr(tmp, '%')))
|
|
*p = '\0';
|
|
|
|
if (sa.sa.sa_family == AF_INET6)
|
|
snprintf(addr, sizeof(addr), "[%s]", tmp);
|
|
else
|
|
snprintf(addr, sizeof(addr), "%s", tmp);
|
|
|
|
snprintf(redirect, sizeof(redirect), "%s:%d", target->redirect.addr,
|
|
target->redirect.port);
|
|
|
|
if (strcmp(target->redirect.addr, addr)) {
|
|
text_key_add(conn, "TargetAddress", redirect);
|
|
res = true;
|
|
}
|
|
|
|
out:
|
|
return res;
|
|
}
|