/* * Copyright (C) 2002 - 2003 Ardis Technologies * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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; }