| /* |
| * 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; |
| } |