blob: 7a2bb818a09bbedf240f0e54c528a86409f6f06c [file] [log] [blame]
/*
* Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <ctype.h>
#include <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;
}