/* * Copyright (C) 2005 FUJITA Tomonori * 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. * * 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 #include #include "iscsid.h" #define BUFSIZE 4096 #define CONFIG_FILE "/etc/iscsi-scstd.conf" /* * Index must match ISCSI_USER_DIR_* and ISCSI_USER_DIR_OUTGOING must * be the last to confirm expectations of __config_account_add()!! */ struct iscsi_key user_keys[] = { { .name = "IncomingUser",}, { .name = "OutgoingUser",}, { .name = 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); struct iscsi_attr *iscsi_attr_lookup_by_sysfs_name( struct __qelem *attrs_list, const char *sysfs_name) { struct iscsi_attr *attr; list_for_each_entry(attr, attrs_list, ulist) { if (!strcmp(attr->sysfs_name, sysfs_name)) return attr; } return NULL; } struct iscsi_attr *iscsi_attr_lookup_by_key( struct __qelem *attrs_list, const char *key) { struct iscsi_attr *attr; list_for_each_entry(attr, attrs_list, ulist) { if (!strcmp(attr->attr_key, key)) return attr; } return NULL; } static void __iscsi_attr_destroy(struct iscsi_attr *attr, int del) { if (!attr) return; if (del) list_del(&attr->ulist); free((void *)attr->attr_key); free((void *)attr->attr_value); free(attr); return; } void iscsi_attr_destroy(struct iscsi_attr *attr) { __iscsi_attr_destroy(attr, 1); } void iscsi_attrs_free(struct __qelem *attrs_list) { struct iscsi_attr *attr, *t; list_for_each_entry_safe(attr, t, attrs_list, ulist) { iscsi_attr_destroy(attr); } return; } int iscsi_attr_create(int attr_size, struct __qelem *attrs_list, const char *sysfs_name_tmpl, const char *key, const char *val, u32 mode, struct iscsi_attr **res_attr) { int res = 0; struct iscsi_attr *attr; int num = 0; if (iscsi_attr_lookup_by_key(attrs_list, key) != NULL) { log_error("Attribute %s already exists", key); res = -EINVAL; goto out; } attr = malloc(attr_size); if (attr == NULL) goto out; memset(attr, 0, attr_size); attr->attr_key = strdup(key); if (attr->attr_key == NULL) { log_error("Unable to duplicate attr_key %s", key); res = -ENOMEM; goto out_free; } if ((val != NULL) && (*val != '\0')) { attr->attr_value = strdup(val); if (attr->attr_value == NULL) { log_error("Unable to duplicate attr_value %s", val); res = -ENOMEM; goto out_free; } } attr->sysfs_mode = mode; if (sysfs_name_tmpl == NULL) goto add; if (sysfs_name_tmpl != NULL) { strlcpy(attr->sysfs_name, sysfs_name_tmpl, sizeof(attr->sysfs_name)); if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL) goto add; } while (1) { if (num == 0) num++; snprintf(attr->sysfs_name, sizeof(attr->sysfs_name), "%s%d", sysfs_name_tmpl, num); if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL) break; num++; } add: list_add_tail(attr, attrs_list); *res_attr = attr; out: return res; out_free: __iscsi_attr_destroy(attr, 0); goto out; } int iscsi_attr_replace(struct __qelem *attrs_list, const char *sysfs_name, char *raw_value) { int res = 0; struct iscsi_attr *attr, *a; char *key, *val, *new_key, *new_val; key = config_sep_string(&raw_value); val = config_sep_string(&raw_value); attr = iscsi_attr_lookup_by_sysfs_name(attrs_list, sysfs_name); if (attr == NULL) { log_error("Unknown parameter %s\n", sysfs_name); res = -EINVAL; goto out; } a = iscsi_attr_lookup_by_key(attrs_list, key); if ((a != NULL) && (a != attr)) { log_error("Attr %s (sysfs_name %s) already exists\n", key, a->sysfs_name); res = -EEXIST; goto out; } new_key = strdup(key); if (new_key == NULL) { log_error("Unable to duplicate attr_key %s", key); res = -ENOMEM; goto out; } if ((val != NULL) && (*val != '\0')) { new_val = strdup(val); if (new_val == NULL) { log_error("Unable to duplicate attr_value %s", val); res = -ENOMEM; free(new_key); goto out; } } else new_val = NULL; free((void *)attr->attr_key); attr->attr_key = new_key; free((void *)attr->attr_value); attr->attr_value = new_val; out: return res; } static struct __qelem *account_list_get(struct target *target, int dir) { struct __qelem *list = NULL; if (target != NULL) { list = (dir == ISCSI_USER_DIR_INCOMING) ? &target->target_in_accounts : &target->target_out_accounts; } else list = (dir == ISCSI_USER_DIR_INCOMING) ? &discovery_users_in : &discovery_users_out; return list; } char *config_sep_string(char **pp) { char *p; char *q; static char blank = '\0'; if ((pp == NULL) || (*pp == NULL)) return ␣ for (p = *pp; isspace(*p) || (*p == '='); p++) ; for (q = p; (*q != '\0') && !isspace(*q) && (*q != '='); q++) ; if (*q != '\0') *q++ = '\0'; *pp = q; return p; } static char *config_gets(char *buf, int size, const char *data, int *offset) { int offs = *offset, i = 0; while ((i < size-1) && (data[offs] != '\n') && (data[offs] != ';') && (data[offs] != '\0')) buf[i++] = data[offs++]; if ((i == 0) && (data[offs] == '\0')) return NULL; if (data[offs] != '\0') offs++; *offset = offs; buf[i] = '\0'; return buf; } int accounts_empty(u32 tid, int dir) { struct target *target; struct __qelem *list; if (tid) { target = target_find_by_id(tid); if (target == NULL) return 0; } else target = NULL; list = account_list_get(target, dir); return list_empty(list); } static struct iscsi_attr *__account_lookup_by_name(struct target *target, int dir, const char *name) { struct __qelem *list; list = account_list_get(target, dir); return iscsi_attr_lookup_by_key(list, name); } static struct iscsi_attr *account_lookup_by_name(u32 tid, int dir, const char *name) { struct target *target; if (tid) { target = target_find_by_id(tid); if (target == NULL) return NULL; } else target = NULL; return __account_lookup_by_name(target, dir, name); } struct iscsi_attr *account_get_first(u32 tid, int dir) { struct target *target; struct __qelem *list; struct iscsi_attr *user; if (tid) { target = target_find_by_id(tid); if (target == NULL) return NULL; } else target = NULL; list = account_list_get(target, dir); list_for_each_entry(user, list, ulist) { return user; } return NULL; } struct iscsi_attr *account_lookup_by_sysfs_name(struct target *target, int dir, const char *sysfs_name) { struct __qelem *list; list = account_list_get(target, dir); return iscsi_attr_lookup_by_sysfs_name(list, sysfs_name); } int config_account_query(u32 tid, int dir, const char *name, char *pass) { struct iscsi_attr *user; if (!(user = account_lookup_by_name(tid, dir, name))) return -ENOENT; strlcpy(pass, ISCSI_USER_PASS(user), ISCSI_NAME_LEN); return 0; } int config_account_list(u32 tid, int dir, u32 *cnt, u32 *overflow, char *buf, size_t buf_sz) { struct target *target; struct __qelem *list; struct iscsi_attr *user; *cnt = *overflow = 0; if (tid) { target = target_find_by_id(tid); if (target == NULL) return -ENOENT; } else target = NULL; list = account_list_get(target, dir); if (!list) return -ENOENT; list_for_each_entry(user, list, ulist) { if (buf_sz >= ISCSI_NAME_LEN) { strlcpy(buf, ISCSI_USER_NAME(user), ISCSI_NAME_LEN); buf_sz -= ISCSI_NAME_LEN; buf += ISCSI_NAME_LEN; *cnt += 1; } else *overflow += 1; } return 0; } static void account_destroy(struct iscsi_attr *user) { iscsi_attr_destroy(user); return; } void accounts_free(struct __qelem *accounts_list) { iscsi_attrs_free(accounts_list); return; } int config_account_del(u32 tid, int dir, char *name, u32 cookie) { struct iscsi_attr *user; int res = 0; struct target *target; if (!name) { log_error("%s", "Name expected"); res = -EINVAL; goto out; } if (tid) { target = target_find_by_id(tid); if (target == NULL) { log_error("Target %d not found", tid); res = -ESRCH; goto out; } } else target = NULL; user = __account_lookup_by_name(target, dir, name); if (user == NULL) { log_error("User %s not found", name); res = -ENOENT; goto out; } log_debug(1, "Deleting %s user %s (%p, target %s, sysfs name %s)", (dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming", ISCSI_USER_NAME(user), user, target ? target->name : "discovery", user->sysfs_name); res = kernel_user_del(target, user, cookie); if (res != 0) goto out; account_destroy(user); out: return res; } static int account_create(struct __qelem *list, const char *sysfs_name, const char *name, const char *pass, struct iscsi_attr **res_user) { return iscsi_attr_create(sizeof(struct iscsi_attr), list, sysfs_name, name, pass, 0600, res_user); } int account_replace(struct target *target, int direction, const char *sysfs_name, char *value) { struct __qelem *list; list = account_list_get(target, direction); return iscsi_attr_replace(list, sysfs_name, value); } int __config_account_add(struct target *target, int dir, char *name, char *pass, const char *sysfs_name, int send_to_kern, u32 cookie) { int err = 0; struct iscsi_attr *user; struct __qelem *list; if (!name || !pass || (*name == '\0') || (*pass == '\0')) { log_error("%s", "Name or password is NULL"); err = -EINVAL; goto out; } /* 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); err = -EINVAL; goto out; } if (dir > ISCSI_USER_DIR_OUTGOING) { log_error("Wrong direction %d\n", dir); err = -EINVAL; goto out; } if (sysfs_name == NULL) sysfs_name = (dir == ISCSI_USER_DIR_OUTGOING) ? user_keys[ISCSI_USER_DIR_OUTGOING].name : user_keys[ISCSI_USER_DIR_INCOMING].name; list = account_list_get(target, dir); if (dir == ISCSI_USER_DIR_OUTGOING) { struct iscsi_attr *old; list_for_each_entry(old, list, ulist) { log_warning("Only one outgoing %s account is " "supported. Replacing the old one.\n", target ? "target" : "discovery"); account_destroy(old); break; } } err = account_create(list, sysfs_name, name, pass, &user); if (err != 0) goto out; if (send_to_kern && (sysfs_name != NULL)) { err = kernel_user_add(target, user, cookie); if (err != 0) goto out_destroy; } log_debug(1, "User %s (%p, sysfs name %s) added to target %s " "(direction %s)", ISCSI_USER_NAME(user), user, user->sysfs_name, target ? target->name : "discovery", (dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming"); out: return err; out_destroy: account_destroy(user); goto out; } int config_account_add(u32 tid, int dir, char *name, char *pass, const char *sysfs_name, u32 cookie) { int err = 0; struct target *target; if (tid) { target = target_find_by_id(tid); if (target == NULL) { err = -ENOENT; goto out; } } else target = NULL; err = __config_account_add(target, dir, name, pass, sysfs_name, 1, cookie); out: return err; } /* * Access control code */ typedef union { struct sockaddr sa; struct sockaddr_in sa_in; struct sockaddr_in6 sa_in6; } sockaddress; static int netmask_match_v6(const struct sockaddr_in6 *sa1, const struct sockaddr_in6 *sa2, uint32_t mbit) { uint16_t mask, a1[8], a2[8]; int i; for (i = 0; i < 8; i++) { a1[i] = ntohs(sa1->sin6_addr.s6_addr16[i]); a2[i] = ntohs(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(const struct sockaddr_in *sa1, const struct sockaddr_in *sa2, uint32_t mbit) { uint32_t s1, s2, mask = ~((1 << (32 - mbit)) - 1); s1 = htonl(sa1->sin_addr.s_addr); s2 = htonl(sa2->sin_addr.s_addr); if (~mask & s1) return 0; if (!((mask & s2) ^ (mask & s1))) return 1; return 0; } static int netmask_match(const sockaddress *sa1, const sockaddress *sa2, char *buf) { unsigned long mbit; uint8_t family = sa1->sa.sa_family; mbit = strtoul(buf, NULL, 0); if (mbit == ULONG_MAX || (family == AF_INET && mbit > 31) || (family == AF_INET6 && mbit > 127)) return 0; if (family == AF_INET) return netmask_match_v4(&sa1->sa_in, &sa2->sa_in, mbit); return netmask_match_v6(&sa1->sa_in6, &sa2->sa_in6, mbit); } static int address_match(const sockaddress *sa1, const sockaddress *sa2) { if (sa1->sa.sa_family == AF_INET) return sa1->sa_in.sin_addr.s_addr == sa2->sa_in.sin_addr.s_addr; else { const struct in6_addr *a1, *a2; a1 = &sa1->sa_in6.sin6_addr; a2 = &sa2->sa_in6.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) { sockaddress from; 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, ","))) { struct addrinfo hints, *res; while (isblank(*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((sockaddress *)res->ai_addr, &from, q); else err = address_match((sockaddress *)res->ai_addr, &from); freeaddrinfo(res); if (err) break; } return err; } static int initiator_match(u32 tid, int fd, const 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 the 'reload feature'. It's slow, but that 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'; p = buf; while (!isblank(*p) && (*p != '\0')) p++; if (*p == '\0') continue; *p = '\0'; p++; if (target_find_id_by_name(buf) != tid && strcmp(buf, "ALL")) continue; err = __initiator_match(fd, p); break; } fclose(fp); return err; } int config_initiator_access_allowed(u32 tid, int fd) { if (initiator_match(tid, fd, "/etc/initiators.deny") && !initiator_match(tid, fd, "/etc/initiators.allow")) return 0; else return 1; } /* * Main configuration code */ int config_target_create(u32 *tid, char *name) { int err; struct target *target; err = target_create(name, &target); if (err != 0) goto out; err = target_add(target, tid, 0); if (err != 0) goto out_free; out: return err; out_free: target_free(target); goto out; } int config_target_destroy(u32 tid) { int err; if ((err = target_del(tid, 0)) < 0) return err; return err; } int config_params_get(u32 tid, u64 sid, int type, struct iscsi_param *params) { int err, i; struct target *target; if (sid != 0) { err = kernel_params_get(tid, sid, type, params); goto out; } err = 0; target = target_find_by_id(tid); if (target == NULL) { log_error("target %d not found", tid); err = -EINVAL; goto out; } if (type == key_session) { for (i = 0; i < session_key_last; i++) params[i].val = target->session_params[i]; } else { for (i = 0; i < target_key_last; i++) params[i].val = target->target_params[i]; } out: return err; } int config_params_set(u32 tid, u64 sid, int type, u32 partial, struct iscsi_param *params) { int err, i; struct target *target; if (sid != 0) { err = kernel_params_set(tid, sid, type, partial, params); goto out; } err = 0; target = target_find_by_id(tid); if (target == 0) { log_error("target %d not found", tid); err = -EINVAL; goto out; } if (partial == 0) partial = (typeof(partial))-1; if (type == key_session) { for (i = 0; i < session_key_last; i++) { uint32_t in_val = params[i].val; if (partial & (1 << i)) { err = params_check_val(session_keys, i, ¶ms[i].val); if (err < 0) { log_error("%s: Wrong value %u->%u for session parameter %s\n", __func__, in_val, params[i].val, session_keys[i].name); goto out; } } } for (i = 0; i < session_key_last; i++) { if (partial & (1 << i)) target->session_params[i] = params[i].val; } } else { for (i = 0; i < target_key_last; i++) { uint32_t in_val = params[i].val; if (partial & (1 << i)) { err = params_check_val(target_keys, i, ¶ms[i].val); if (err < 0) { log_error("%s: Wrong value %u->%u for target parameter %s\n", __func__, in_val, params[i].val, target_keys[i].name); goto out; } } } for (i = 0; i < target_key_last; i++) { if (partial & (1 << i)) target->target_params[i] = params[i].val; } } out: return err; } int config_parse_main(const char *data, u32 cookie) { char buf[BUFSIZE]; char *p, *q, *n; int idx, offset = 0; u32 val; int res = 0; struct target *target = NULL; int global_section = 1; /* supposed to be bool and true */ int parsed_something = 0; /* supposed to be bool and false */ int stop_on_errors = (cookie != 0); while (config_gets(buf, sizeof(buf), data, &offset)) { parsed_something = 1; /* * If stop_on_errors is false, let's always continue parsing * and only report errors. */ if (stop_on_errors && (res != 0)) goto out_target_free; q = buf; p = config_sep_string(&q); if ((*p == '#') || (*p == '\0')) continue; if (!strcasecmp(p, "Target")) { global_section = 0; if (target != NULL) { res = target_add(target, NULL, cookie); if (res != 0) target_free(target); } target = NULL; p = config_sep_string(&q); if (*p == '\0') { log_error("Target name required on %s\n", q); continue; } n = config_sep_string(&q); if (*n != '\0') { log_error("Unexpected parameter value %s\n", n); res = -EINVAL; continue; } log_debug(1, "Creating target %s", p); res = target_create(p, &target); if (res != 0) goto out; } else if (!strcasecmp(p, "Alias") && target) { ; } else if (!((idx = params_index_by_name(p, target_keys)) < 0) && (target != NULL)) { char *str = config_sep_string(&q); n = config_sep_string(&q); if (*n != '\0') { log_error("Unexpected parameter value %s\n", n); res = -EINVAL; continue; } res = params_str_to_val(target_keys, idx, str, &val); if (res < 0) { log_error("Wrong value %s for parameter %s\n", str, target_keys[idx].name); continue; } uint32_t in_val = val; res = params_check_val(target_keys, idx, &val); if (res < 0) { log_error("%s: Wrong value %u->%u for target parameter %s\n", __func__, in_val, val, target_keys[idx].name); continue; } target->target_params[idx] = val; } else if (!((idx = params_index_by_name(p, session_keys)) < 0) && (target != NULL)) { char *str = config_sep_string(&q); n = config_sep_string(&q); if (*n != '\0') { log_error("Unexpected parameter value %s\n", n); res = -EINVAL; continue; } res = params_str_to_val(session_keys, idx, str, &val); if (res < 0) { log_error("Wrong value %s for parameter %s\n", str, session_keys[idx].name); continue; } uint32_t in_val = val; res = params_check_val(session_keys, idx, &val); if (res < 0) { log_error("%s: Wrong value %u->%u for session parameter %s\n", __func__, in_val, val, session_keys[idx].name); continue; } target->session_params[idx] = val; } else if (!((idx = params_index_by_name_numwild(p, user_keys)) < 0) && ((target != NULL) || global_section)) { char *name, *pass; name = config_sep_string(&q); pass = config_sep_string(&q); n = config_sep_string(&q); if (*n != '\0') { log_error("Unexpected parameter value %s\n", n); res = -EINVAL; continue; } res = __config_account_add(target, idx, name, pass, p, (target == 0), 0); if (res < 0) continue; } else if (global_section && (!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME) || !strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME))) continue; else { log_error("Unknown or unexpected param: %s\n", p); res = -EINVAL; continue; } } if (stop_on_errors && (res != 0)) goto out_target_free; if (target != NULL) { res = target_add(target, NULL, cookie); if (res != 0) goto out_target_free; } out: if (stop_on_errors) { if ((res == 0) && !parsed_something) res = -ENOENT; } else res = 0; return res; out_target_free: if (target != NULL) target_free(target); goto out; } static int config_isns_load(const char *config) { char buf[BUFSIZE]; int offset = 0; char *p, *q; while (config_gets(buf, sizeof(buf), config, &offset)) { q = buf; p = config_sep_string(&q); if ((*p == '\0') || (*p == '#')) continue; if (!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME)) { free(isns_server); isns_server = strdup(config_sep_string(&q)); } else if (!strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME)) { char *str = config_sep_string(&q); if (!strcasecmp(str, "No")) isns_access_control = 0; else isns_access_control = 1; } } return 0; } int config_load(const char *config_name) { int i, err = 0, rc; int config; const char *cname; int size; char *buf; if (config_name != NULL) cname = config_name; else cname = CONFIG_FILE; config = open(cname, O_RDONLY); if (config == -1) { if ((errno == ENOENT) && (config_name == NULL)) { goto out; } else { err = -errno; log_error("Open config file %s failed: %s", cname, strerror(errno)); goto out; } } size = lseek(config, 0, SEEK_END); if (size < 0) { err = -errno; log_error("lseek() failed: %s", strerror(errno)); goto out_close; } buf = malloc(size+1); if (buf == NULL) { err = -ENOMEM; log_error("malloc() failed: %s", strerror(-err)); goto out_close; } rc = lseek(config, 0, SEEK_SET); if (rc < 0) { err = -errno; log_error("lseek() failed: %s", strerror(errno)); goto out_free; } i = 0; do { rc = read(config, &buf[i], size - i); if (rc < 0) { err = -errno; log_error("read() failed: %s", strerror(errno)); goto out_free; } else if (rc == 0) break; i += rc; } while (i < size); size = i; buf[size] = '\0'; err = config_isns_load(buf); if ((err == 0) && (isns_server != NULL)) { rc = isns_init(); if (rc != 0) { log_error("iSNS server %s init failed: %d", isns_server, rc); isns_exit(); } } config_parse_main(buf, 0); out_free: free(buf); out_close: close(config); out: return err; }