diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc index 4821bdb030c1..451245b8d5fa 100644 --- a/usr.sbin/ctld/ctld.cc +++ b/usr.sbin/ctld/ctld.cc @@ -1,2631 +1,2629 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "conf.h" #include "ctld.hh" #include "isns.hh" bool proxy_mode = false; static volatile bool sighup_received = false; static volatile bool sigterm_received = false; static volatile bool sigalrm_received = false; static int kqfd; static int nchildren = 0; static void usage(void) { fprintf(stderr, "usage: ctld [-d][-u][-f config-file]\n"); fprintf(stderr, " ctld -t [-u][-f config-file]\n"); exit(1); } void conf::set_debug(int debug) { conf_debug = debug; } void conf::set_isns_period(int period) { conf_isns_period = period; } void conf::set_isns_timeout(int timeout) { conf_isns_timeout = timeout; } void conf::set_maxproc(int maxproc) { conf_maxproc = maxproc; } void conf::set_timeout(int timeout) { conf_timeout = timeout; } bool conf::set_pidfile_path(std::string_view path) { if (!conf_pidfile_path.empty()) { log_warnx("pidfile specified more than once"); return (false); } conf_pidfile_path = path; return (true); } void conf::open_pidfile() { const char *path; pid_t otherpid; assert(!conf_pidfile_path.empty()); path = conf_pidfile_path.c_str(); log_debugx("opening pidfile %s", path); conf_pidfile = pidfile_open(path, 0600, &otherpid); if (!conf_pidfile) { if (errno == EEXIST) log_errx(1, "daemon already running, pid: %jd.", (intmax_t)otherpid); log_err(1, "cannot open or create pidfile \"%s\"", path); } } void conf::write_pidfile() { conf_pidfile.write(); } void conf::close_pidfile() { conf_pidfile.close(); } #ifdef ICL_KERNEL_PROXY int conf::add_proxy_portal(portal *portal) { conf_proxy_portals.push_back(portal); return (conf_proxy_portals.size() - 1); } portal * conf::proxy_portal(int id) { if (id >= conf_proxy_portals.size()) return (nullptr); return (conf_proxy_portals[id]); } #endif bool auth_group::set_type(const char *str) { auth_type type; if (strcmp(str, "none") == 0) { type = auth_type::NO_AUTHENTICATION; } else if (strcmp(str, "deny") == 0) { type = auth_type::DENY; } else if (strcmp(str, "chap") == 0) { type = auth_type::CHAP; } else if (strcmp(str, "chap-mutual") == 0) { type = auth_type::CHAP_MUTUAL; } else { log_warnx("invalid auth-type \"%s\" for %s", str, label()); return (false); } if (ag_type != auth_type::UNKNOWN && ag_type != type) { log_warnx("cannot set auth-type to \"%s\" for %s; " "already has a different type", str, label()); return (false); } ag_type = type; return (true); } void auth_group::set_type(auth_type type) { assert(ag_type == auth_type::UNKNOWN); ag_type = type; } const struct auth * auth_group::find_auth(std::string_view user) const { auto it = ag_auths.find(std::string(user)); if (it == ag_auths.end()) return (nullptr); return (&it->second); } void auth_group::check_secret_length(const char *user, const char *secret, const char *secret_type) { size_t len; len = strlen(secret); assert(len != 0); if (len > 16) { log_warnx("%s for user \"%s\", %s, is too long; it should be " "at most 16 characters long", secret_type, user, label()); } if (len < 12) { log_warnx("%s for user \"%s\", %s, is too short; it should be " "at least 12 characters long", secret_type, user, label()); } } bool auth_group::add_chap(const char *user, const char *secret) { if (ag_type == auth_type::UNKNOWN) ag_type = auth_type::CHAP; if (ag_type != auth_type::CHAP) { log_warnx("cannot mix \"chap\" authentication with " "other types for %s", label()); return (false); } check_secret_length(user, secret, "secret"); const auto &pair = ag_auths.try_emplace(user, secret); if (!pair.second) { log_warnx("duplicate credentials for user \"%s\" for %s", user, label()); return (false); } return (true); } bool auth_group::add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2) { if (ag_type == auth_type::UNKNOWN) ag_type = auth_type::CHAP_MUTUAL; if (ag_type != auth_type::CHAP_MUTUAL) { log_warnx("cannot mix \"chap-mutual\" authentication " "with other types for %s", label()); return (false); } check_secret_length(user, secret, "secret"); check_secret_length(user, secret2, "mutual secret"); const auto &pair = ag_auths.try_emplace(user, secret, user2, secret2); if (!pair.second) { log_warnx("duplicate credentials for user \"%s\" for %s", user, label()); return (false); } return (true); } bool auth_group::add_initiator_name(std::string_view name) { /* Silently ignore duplicates. */ ag_initiator_names.emplace(name); return (true); } bool auth_group::initiator_permitted(std::string_view initiator_name) const { if (ag_initiator_names.empty()) return (true); return (ag_initiator_names.count(std::string(initiator_name)) != 0); } bool auth_portal::parse(const char *portal) { std::string net(portal); std::string mask; /* Split into 'net' (address) and 'mask'. */ size_t pos = net.find('/'); if (pos != net.npos) { mask = net.substr(pos + 1); if (mask.empty()) return false; net.resize(pos); } if (net.empty()) return false; /* * If 'net' starts with a '[', ensure it ends with a ']' and * force interpreting the address as IPv6. */ bool brackets = net[0] == '['; if (brackets) { net.erase(0, 1); size_t len = net.length(); if (len < 2) return false; if (net[len - 1] != ']') return false; net.resize(len - 1); } /* Parse address from 'net' and set default mask. */ if (brackets || net.find(':') != net.npos) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ap_sa; sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = AF_INET6; if (inet_pton(AF_INET6, net.c_str(), &sin6->sin6_addr) <= 0) return false; ap_mask = sizeof(sin6->sin6_addr) * 8; } else { struct sockaddr_in *sin = (struct sockaddr_in *)&ap_sa; sin->sin_len = sizeof(*sin); sin->sin_family = AF_INET; if (inet_pton(AF_INET, net.c_str(), &sin->sin_addr) <= 0) return false; ap_mask = sizeof(sin->sin_addr) * 8; } /* Parse explicit mask if present. */ if (!mask.empty()) { char *tmp; long m = strtol(mask.c_str(), &tmp, 0); if (m < 0 || m > ap_mask || tmp[0] != 0) return false; ap_mask = m; } return true; } bool auth_group::add_initiator_portal(const char *portal) { auth_portal ap; if (!ap.parse(portal)) { log_warnx("invalid initiator portal \"%s\" for %s", portal, label()); return (false); } ag_initiator_portals.emplace_back(ap); return (true); } bool auth_portal::matches(const struct sockaddr *sa) const { const uint8_t *a, *b; int i; if (ap_sa.ss_family != sa->sa_family) return (false); if (sa->sa_family == AF_INET) { a = (const uint8_t *) &((const struct sockaddr_in *)sa)->sin_addr; b = (const uint8_t *) &((const struct sockaddr_in *)&ap_sa)->sin_addr; } else { a = (const uint8_t *) &((const struct sockaddr_in6 *)sa)->sin6_addr; b = (const uint8_t *) &((const struct sockaddr_in6 *)&ap_sa)->sin6_addr; } for (i = 0; i < ap_mask / 8; i++) { if (a[i] != b[i]) return (false); } if ((ap_mask % 8) != 0) { uint8_t bmask = 0xff << (8 - (ap_mask % 8)); if ((a[i] & bmask) != (b[i] & bmask)) return (false); } return (true); } bool auth_group::initiator_permitted(const struct sockaddr *sa) const { if (ag_initiator_portals.empty()) return (true); for (const auth_portal &ap : ag_initiator_portals) if (ap.matches(sa)) return (true); return (false); } struct auth_group * conf::add_auth_group(const char *name) { const auto &pair = conf_auth_groups.try_emplace(name, std::make_shared(freebsd::stringf("auth-group \"%s\"", name))); if (!pair.second) { log_warnx("duplicated auth-group \"%s\"", name); return (NULL); } return (pair.first->second.get()); } /* * Make it possible to redefine the default auth-group, but only once. */ struct auth_group * conf::define_default_auth_group() { if (conf_default_ag_defined) { log_warnx("duplicated auth-group \"default\""); return (nullptr); } conf_default_ag_defined = true; return (find_auth_group("default").get()); } auth_group_sp conf::find_auth_group(std::string_view name) { auto it = conf_auth_groups.find(std::string(name)); if (it == conf_auth_groups.end()) return {}; return (it->second); } portal_group::portal_group(struct conf *conf, std::string_view name) : pg_conf(conf), pg_options(nvlist_create(0)), pg_name(name) { } struct portal_group * conf::add_portal_group(const char *name) { auto pair = conf_portal_groups.try_emplace(name, iscsi_make_portal_group(this, name)); if (!pair.second) { log_warnx("duplicated portal-group \"%s\"", name); return (nullptr); } return (pair.first->second.get()); } /* * Make it possible to redefine the default portal-group, but only * once. */ struct portal_group * conf::define_default_portal_group() { if (conf_default_pg_defined) { log_warnx("duplicated portal-group \"default\""); return (nullptr); } conf_default_pg_defined = true; return (find_portal_group("default")); } struct portal_group * conf::find_portal_group(std::string_view name) { auto it = conf_portal_groups.find(std::string(name)); if (it == conf_portal_groups.end()) return (nullptr); return (it->second.get()); } bool portal_group::is_dummy() const { if (pg_foreign) return (true); if (pg_portals.empty()) return (true); return (false); } freebsd::addrinfo_up parse_addr_port(const char *address, const char *def_port) { struct addrinfo hints, *ai; int error; std::string addr(address); std::string port(def_port); if (addr[0] == '[') { /* * IPv6 address in square brackets, perhaps with port. */ addr.erase(0, 1); size_t pos = addr.find(']'); if (pos == 0 || pos == addr.npos) return {}; if (pos < addr.length() - 1) { port = addr.substr(pos + 1); if (port[0] != ':' || port.length() < 2) return {}; port.erase(0, 1); } addr.resize(pos); } else { /* * Either IPv6 address without brackets - and without * a port - or IPv4 address. Just count the colons. */ size_t pos = addr.find(':'); if (pos != addr.npos && addr.find(':', pos + 1) == addr.npos) { /* Only a single colon at `pos`. */ if (pos == addr.length() - 1) return {}; port = addr.substr(pos + 1); addr.resize(pos); } } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; error = getaddrinfo(addr.c_str(), port.c_str(), &hints, &ai); if (error != 0) return {}; return freebsd::addrinfo_up(ai); } void portal_group::add_port(struct portal_group_port *port) { pg_ports.emplace(port->target()->name(), port); } void portal_group::remove_port(struct portal_group_port *port) { auto it = pg_ports.find(port->target()->name()); pg_ports.erase(it); } freebsd::nvlist_up portal_group::options() const { return (freebsd::nvlist_up(nvlist_clone(pg_options.get()))); } bool portal_group::add_option(const char *name, const char *value) { return (option_new(pg_options.get(), name, value)); } bool portal_group::set_discovery_auth_group(const char *ag_name) { if (pg_discovery_auth_group != nullptr) { log_warnx("discovery-auth-group for %s " "\"%s\" specified more than once", keyword(), name()); return (false); } pg_discovery_auth_group = pg_conf->find_auth_group(ag_name); if (pg_discovery_auth_group == nullptr) { log_warnx("unknown discovery-auth-group \"%s\" " "for %s \"%s\"", ag_name, keyword(), name()); return (false); } return (true); } bool portal_group::set_dscp(u_int dscp) { if (dscp >= 0x40) { log_warnx("invalid DSCP value %u for %s \"%s\"", dscp, keyword(), name()); return (false); } pg_dscp = dscp; return (true); } void portal_group::set_foreign() { pg_foreign = true; } bool portal_group::set_offload(const char *offload) { if (!pg_offload.empty()) { log_warnx("cannot set offload to \"%s\" for " "%s \"%s\"; already defined", offload, keyword(), name()); return (false); } pg_offload = offload; return (true); } bool portal_group::set_pcp(u_int pcp) { if (pcp > 7) { log_warnx("invalid PCP value %u for %s \"%s\"", pcp, keyword(), name()); return (false); } pg_pcp = pcp; return (true); } bool portal_group::set_redirection(const char *addr) { if (!pg_redirection.empty()) { log_warnx("cannot set redirection to \"%s\" for " "%s \"%s\"; already defined", addr, keyword(), name()); return (false); } pg_redirection = addr; return (true); } void portal_group::set_tag(uint16_t tag) { pg_tag = tag; } void portal_group::verify(struct conf *conf) { if (pg_discovery_auth_group == nullptr) { pg_discovery_auth_group = conf->find_auth_group("default"); assert(pg_discovery_auth_group != nullptr); } if (pg_discovery_filter == discovery_filter::UNKNOWN) pg_discovery_filter = discovery_filter::NONE; if (!pg_redirection.empty()) { if (!pg_ports.empty()) { log_debugx("%s \"%s\" assigned to target, " "but configured for redirection", keyword(), name()); } pg_assigned = true; } else if (!pg_ports.empty()) { pg_assigned = true; } else { if (pg_name != "default") log_warnx("%s \"%s\" not assigned " "to any target", keyword(), name()); pg_assigned = false; } } /* * Try to reuse a socket for 'newp' from an existing socket in one of * our portals. */ bool portal_group::reuse_socket(struct portal &newp) { for (portal_up &portal : pg_portals) { if (newp.reuse_socket(*portal)) return (true); } return (false); } int portal_group::open_sockets(struct conf &oldconf) { int cumulated_error = 0; if (pg_foreign) return (0); if (!pg_assigned) { log_debugx("not listening on %s \"%s\", " "not assigned to any target", keyword(), name()); return (0); } for (portal_up &portal : pg_portals) { /* * Try to find already open portal and reuse the * listening socket. We don't care about what portal * or portal group that was, what matters is the * listening address. */ if (oldconf.reuse_portal_group_socket(*portal)) continue; if (!portal->init_socket()) { cumulated_error++; continue; } } return (cumulated_error); } void portal_group::close_sockets() { for (portal_up &portal : pg_portals) { if (portal->socket() < 0) continue; log_debugx("closing socket for %s, %s \"%s\"", portal->listen(), keyword(), name()); portal->close(); } } bool conf::add_isns(const char *addr) { if (conf_isns.count(addr) > 0) { log_warnx("duplicate iSNS address %s", addr); return (false); } freebsd::addrinfo_up ai = parse_addr_port(addr, "3205"); if (!ai) { log_warnx("invalid iSNS address %s", addr); return (false); } /* * XXX: getaddrinfo(3) may return multiple addresses; we should turn * those into multiple servers. */ conf_isns.emplace(addr, isns(addr, std::move(ai))); return (true); } freebsd::fd_up isns::connect() { freebsd::fd_up s; s = socket(i_ai->ai_family, i_ai->ai_socktype, i_ai->ai_protocol); if (!s) { log_warn("socket(2) failed for %s", addr()); return (s); } if (::connect(s, i_ai->ai_addr, i_ai->ai_addrlen)) { log_warn("connect(2) failed for %s", addr()); s.reset(); } return (s); } bool isns::send_request(int s, struct isns_req req) { if (!req.send(s)) { log_warn("send(2) failed for %s", addr()); return (false); } if (!req.receive(s)) { log_warn("receive(2) failed for %s", addr()); return (false); } uint32_t error = req.get_status(); if (error != 0) { log_warnx("iSNS %s error %u for %s", req.descr(), error, addr()); return (false); } return (true); } struct isns_req conf::isns_register_request(const char *hostname) { const struct portal_group *pg; isns_req req(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT, "register"); req.add_str(32, conf_first_target->name()); req.add_delim(); req.add_str(1, hostname); req.add_32(2, 2); /* 2 -- iSCSI */ req.add_32(6, conf_isns_period); for (const auto &kv : conf_portal_groups) { pg = kv.second.get(); if (!pg->assigned()) continue; for (const portal_up &portal : pg->portals()) { req.add_addr(16, portal->ai()); req.add_port(17, portal->ai()); } } for (const auto &kv : conf_targets) { const struct target *target = kv.second.get(); req.add_str(32, target->name()); req.add_32(33, 1); /* 1 -- Target*/ if (target->has_alias()) req.add_str(34, target->alias()); for (const port *port : target->ports()) { pg = port->portal_group(); if (pg == nullptr) continue; req.add_32(51, pg->tag()); for (const portal_up &portal : pg->portals()) { req.add_addr(49, portal->ai()); req.add_port(50, portal->ai()); } } } return (req); } struct isns_req conf::isns_check_request(const char *hostname) { isns_req req(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT, "check"); req.add_str(32, conf_first_target->name()); req.add_str(1, hostname); req.add_delim(); req.add(2, 0, NULL); return (req); } struct isns_req conf::isns_deregister_request(const char *hostname) { isns_req req(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT, "deregister"); req.add_str(32, conf_first_target->name()); req.add_delim(); req.add_str(1, hostname); return (req); } void conf::isns_register_targets(struct isns *isns, struct conf *oldconf) { int error; char hostname[256]; if (conf_targets.empty() || conf_portal_groups.empty()) return; start_timer(conf_isns_timeout); freebsd::fd_up s = isns->connect(); if (!s) { stop_timer(); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); if (oldconf == nullptr || oldconf->conf_first_target == nullptr) oldconf = this; isns->send_request(s, oldconf->isns_deregister_request(hostname)); isns->send_request(s, isns_register_request(hostname)); s.reset(); stop_timer(); } void conf::isns_check(struct isns *isns) { int error; char hostname[256]; if (conf_targets.empty() || conf_portal_groups.empty()) return; start_timer(conf_isns_timeout); freebsd::fd_up s = isns->connect(); if (!s) { stop_timer(); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); if (!isns->send_request(s, isns_check_request(hostname))) { isns->send_request(s, isns_deregister_request(hostname)); isns->send_request(s, isns_register_request(hostname)); } s.reset(); stop_timer(); } void conf::isns_deregister_targets(struct isns *isns) { int error; char hostname[256]; if (conf_targets.empty() || conf_portal_groups.empty()) return; start_timer(conf_isns_timeout); freebsd::fd_up s = isns->connect(); if (!s) return; error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); isns->send_request(s, isns_deregister_request(hostname)); s.reset(); stop_timer(); } void conf::isns_schedule_update() { if (!conf_isns.empty()) start_timer((conf_isns_period + 2) / 3); } void conf::isns_update() { stop_timer(); for (auto &kv : conf_isns) isns_check(&kv.second); isns_schedule_update(); } bool kports::add_port(std::string &name, uint32_t ctl_port) { const auto &pair = pports.try_emplace(name, name, ctl_port); if (!pair.second) { log_warnx("duplicate kernel port \"%s\" (%u)", name.c_str(), ctl_port); return (false); } return (true); } bool kports::has_port(std::string_view name) { return (pports.count(std::string(name)) > 0); } struct pport * kports::find_port(std::string_view name) { auto it = pports.find(std::string(name)); if (it == pports.end()) return (nullptr); return (&it->second); } port::port(struct target *target) : p_target(target) { target->add_port(this); } void port::clear_references() { p_target->remove_port(this); } portal_group_port::portal_group_port(struct target *target, struct portal_group *pg, auth_group_sp ag) : port(target), p_auth_group(ag), p_portal_group(pg) { p_portal_group->add_port(this); } portal_group_port::portal_group_port(struct target *target, struct portal_group *pg, uint32_t ctl_port) : port(target), p_portal_group(pg) { p_ctl_port = ctl_port; p_portal_group->add_port(this); } bool portal_group_port::is_dummy() const { return (p_portal_group->is_dummy()); } void portal_group_port::clear_references() { p_portal_group->remove_port(this); port::clear_references(); } bool conf::add_port(struct target *target, struct portal_group *pg, auth_group_sp ag) { std::string name = freebsd::stringf("%s-%s", pg->name(), target->name()); const auto &pair = conf_ports.try_emplace(name, pg->create_port(target, ag)); if (!pair.second) { log_warnx("duplicate port \"%s\"", name.c_str()); return (false); } return (true); } bool conf::add_port(struct target *target, struct portal_group *pg, uint32_t ctl_port) { std::string name = freebsd::stringf("%s-%s", pg->name(), target->name()); const auto &pair = conf_ports.try_emplace(name, pg->create_port(target, ctl_port)); if (!pair.second) { log_warnx("duplicate port \"%s\"", name.c_str()); return (false); } return (true); } bool conf::add_port(struct target *target, struct pport *pp) { std::string name = freebsd::stringf("%s-%s", pp->name(), target->name()); const auto &pair = conf_ports.try_emplace(name, std::make_unique(target, pp)); if (!pair.second) { log_warnx("duplicate port \"%s\"", name.c_str()); return (false); } pp->link(); return (true); } bool conf::add_port(struct kports &kports, struct target *target, int pp, int vp) { struct pport *pport; std::string pname = freebsd::stringf("ioctl/%d/%d", pp, vp); pport = kports.find_port(pname); if (pport != NULL) return (add_port(target, pport)); std::string name = pname + "-" + target->name(); const auto &pair = conf_ports.try_emplace(name, std::make_unique(target, pp, vp)); if (!pair.second) { log_warnx("duplicate port \"%s\"", name.c_str()); return (false); } return (true); } const struct port * portal_group::find_port(std::string_view target) const { auto it = pg_ports.find(std::string(target)); if (it == pg_ports.end()) return (nullptr); return (it->second); } target::target(struct conf *conf, const char *keyword, std::string_view name) : t_conf(conf), t_name(name) { t_label = freebsd::stringf("%s \"%s\"", keyword, t_name.c_str()); } struct target * conf::add_target(const char *name) { if (!valid_iscsi_name(name, log_warnx)) return (nullptr); /* * RFC 3722 requires us to normalize the name to lowercase. */ std::string t_name(name); for (char &c : t_name) c = tolower(c); auto const &pair = conf_targets.try_emplace(t_name, iscsi_make_target(this, t_name)); if (!pair.second) { log_warnx("duplicated target \"%s\"", name); return (NULL); } if (conf_first_target == nullptr) conf_first_target = pair.first->second.get(); return (pair.first->second.get()); } struct target * conf::find_target(std::string_view name) { auto it = conf_targets.find(std::string(name)); if (it == conf_targets.end()) return (nullptr); return (it->second.get()); } bool target::use_private_auth(const char *keyword) { if (t_private_auth) return (true); if (t_auth_group != nullptr) { log_warnx("cannot use both auth-group and %s for %s", keyword, label()); return (false); } t_auth_group = std::make_shared(t_label); t_private_auth = true; return (true); } bool target::add_chap(const char *user, const char *secret) { if (!use_private_auth("chap")) return (false); return (t_auth_group->add_chap(user, secret)); } bool target::add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2) { if (!use_private_auth("chap-mutual")) return (false); return (t_auth_group->add_chap_mutual(user, secret, user2, secret2)); } bool target::add_lun(u_int id, const char *lun_label, const char *lun_name) { struct lun *t_lun; if (id >= MAX_LUNS) { log_warnx("%s too big for %s", lun_label, label()); return (false); } if (t_luns[id] != NULL) { log_warnx("duplicate %s for %s", lun_label, label()); return (false); } t_lun = t_conf->find_lun(lun_name); if (t_lun == NULL) { log_warnx("unknown LUN named %s used for %s", lun_name, label()); return (false); } t_luns[id] = t_lun; return (true); } bool target::set_alias(std::string_view alias) { if (has_alias()) { log_warnx("alias for %s specified more than once", label()); return (false); } t_alias = alias; return (true); } bool target::set_auth_group(const char *ag_name) { if (t_auth_group != nullptr) { if (t_private_auth) log_warnx("cannot use both auth-group and explicit " "authorisations for %s", label()); else log_warnx("auth-group for %s " "specified more than once", label()); return (false); } t_auth_group = t_conf->find_auth_group(ag_name); if (t_auth_group == nullptr) { log_warnx("unknown auth-group \"%s\" for %s", ag_name, label()); return (false); } return (true); } bool target::set_auth_type(const char *type) { if (!use_private_auth("auth-type")) return (false); return (t_auth_group->set_type(type)); } bool target::set_physical_port(std::string_view pport) { if (!t_pport.empty()) { log_warnx("cannot set multiple physical ports for target " "\"%s\"", name()); return (false); } t_pport = pport; return (true); } bool target::set_redirection(const char *addr) { if (!t_redirection.empty()) { log_warnx("cannot set redirection to \"%s\" for " "%s; already defined", addr, label()); return (false); } t_redirection = addr; return (true); } struct lun * target::start_lun(u_int id, const char *lun_label, const char *lun_name) { if (id >= MAX_LUNS) { log_warnx("%s too big for %s", lun_label, label()); return (nullptr); } if (t_luns[id] != NULL) { log_warnx("duplicate %s for %s", lun_label, label()); return (nullptr); } struct lun *new_lun = t_conf->add_lun(lun_name); if (new_lun == nullptr) return (nullptr); new_lun->set_scsiname(lun_name); t_luns[id] = new_lun; return (new_lun); } void target::add_port(struct port *port) { t_ports.push_back(port); } void target::remove_port(struct port *port) { t_ports.remove(port); } void target::remove_lun(struct lun *lun) { /* XXX: clang is not able to deduce the type without the cast. */ std::replace(t_luns.begin(), t_luns.end(), lun, static_cast(nullptr)); } void target::verify() { if (t_auth_group == nullptr) { t_auth_group = t_conf->find_auth_group("default"); assert(t_auth_group != nullptr); } if (t_ports.empty()) { struct portal_group *pg = default_portal_group(); assert(pg != NULL); t_conf->add_port(this, pg, nullptr); } bool found = std::any_of(t_luns.begin(), t_luns.end(), [](struct lun *lun) { return (lun != nullptr); }); if (!found && t_redirection.empty()) log_warnx("no LUNs defined for %s", label()); if (found && !t_redirection.empty()) log_debugx("%s contains LUNs, but configured " "for redirection", label()); } lun::lun(struct conf *conf, std::string_view name) : l_conf(conf), l_options(nvlist_create(0)), l_name(name) { } struct lun * conf::add_lun(const char *name) { const auto &pair = conf_luns.try_emplace(name, std::make_unique(this, name)); if (!pair.second) { log_warnx("duplicated lun \"%s\"", name); return (NULL); } return (pair.first->second.get()); } void conf::delete_target_luns(struct lun *lun) { for (const auto &kv : conf_targets) kv.second->remove_lun(lun); } struct lun * conf::find_lun(std::string_view name) { auto it = conf_luns.find(std::string(name)); if (it == conf_luns.end()) return (nullptr); return (it->second.get()); } static void nvlist_replace_string(nvlist_t *nvl, const char *name, const char *value) { if (nvlist_exists_string(nvl, name)) nvlist_free_string(nvl, name); nvlist_add_string(nvl, name, value); } freebsd::nvlist_up lun::options() const { freebsd::nvlist_up nvl(nvlist_clone(l_options.get())); if (!l_path.empty()) nvlist_replace_string(nvl.get(), "file", l_path.c_str()); nvlist_replace_string(nvl.get(), "ctld_name", l_name.c_str()); if (!nvlist_exists_string(nvl.get(), "scsiname") && !l_scsiname.empty()) nvlist_add_string(nvl.get(), "scsiname", l_scsiname.c_str()); return (nvl); } bool lun::add_option(const char *name, const char *value) { return (option_new(l_options.get(), name, value)); } bool lun::set_backend(std::string_view value) { if (!l_backend.empty()) { log_warnx("backend for lun \"%s\" specified more than once", name()); return (false); } l_backend = value; return (true); } bool lun::set_blocksize(size_t value) { if (l_blocksize != 0) { log_warnx("blocksize for lun \"%s\" specified more than once", name()); return (false); } l_blocksize = value; return (true); } bool lun::set_ctl_lun(uint32_t value) { if (l_ctl_lun >= 0) { log_warnx("ctl_lun for lun \"%s\" specified more than once", name()); return (false); } l_ctl_lun = value; return (true); } bool lun::set_device_type(uint8_t device_type) { if (device_type > 15) { log_warnx("invalid device-type \"%u\" for lun \"%s\"", device_type, name()); return (false); } l_device_type = device_type; return (true); } bool lun::set_device_type(const char *value) { const char *errstr; int device_type; if (strcasecmp(value, "disk") == 0 || strcasecmp(value, "direct") == 0) device_type = T_DIRECT; else if (strcasecmp(value, "processor") == 0) device_type = T_PROCESSOR; else if (strcasecmp(value, "cd") == 0 || strcasecmp(value, "cdrom") == 0 || strcasecmp(value, "dvd") == 0 || strcasecmp(value, "dvdrom") == 0) device_type = T_CDROM; else { device_type = strtonum(value, 0, 15, &errstr); if (errstr != NULL) { log_warnx("invalid device-type \"%s\" for lun \"%s\"", value, name()); return (false); } } l_device_type = device_type; return (true); } bool lun::set_device_id(std::string_view value) { if (!l_device_id.empty()) { log_warnx("device_id for lun \"%s\" specified more than once", name()); return (false); } l_device_id = value; return (true); } bool lun::set_path(std::string_view value) { if (!l_path.empty()) { log_warnx("path for lun \"%s\" specified more than once", name()); return (false); } l_path = value; return (true); } void lun::set_scsiname(std::string_view value) { l_scsiname = value; } bool lun::set_serial(std::string_view value) { if (!l_serial.empty()) { log_warnx("serial for lun \"%s\" specified more than once", name()); return (false); } l_serial = value; return (true); } bool lun::set_size(uint64_t value) { if (l_size != 0) { log_warnx("size for lun \"%s\" specified more than once", name()); return (false); } l_size = value; return (true); } bool lun::changed(const struct lun &newlun) const { if (l_backend != newlun.l_backend) { log_debugx("backend for lun \"%s\", CTL lun %d changed; " "removing", name(), l_ctl_lun); return (true); } if (l_blocksize != newlun.l_blocksize) { log_debugx("blocksize for lun \"%s\", CTL lun %d changed; " "removing", name(), l_ctl_lun); return (true); } if (l_device_id != newlun.l_device_id) { log_debugx("device-id for lun \"%s\", CTL lun %d changed; " "removing", name(), l_ctl_lun); return (true); } if (l_path != newlun.l_path) { log_debugx("path for lun \"%s\", CTL lun %d, changed; " "removing", name(), l_ctl_lun); return (true); } if (l_serial != newlun.l_serial) { log_debugx("serial for lun \"%s\", CTL lun %d changed; " "removing", name(), l_ctl_lun); return (true); } return (false); } bool option_new(nvlist_t *nvl, const char *name, const char *value) { int error; if (nvlist_exists_string(nvl, name)) { log_warnx("duplicated option \"%s\"", name); return (false); } nvlist_add_string(nvl, name, value); error = nvlist_error(nvl); if (error != 0) { log_warnc(error, "failed to add option \"%s\"", name); return (false); } return (true); } bool lun::verify() { if (l_backend.empty()) l_backend = "block"; if (l_backend == "block") { if (l_path.empty()) { log_warnx("missing path for lun \"%s\"", name()); return (false); } } else if (l_backend == "ramdisk") { if (l_size == 0) { log_warnx("missing size for ramdisk-backed lun \"%s\"", name()); return (false); } if (!l_path.empty()) { log_warnx("path must not be specified " "for ramdisk-backed lun \"%s\"", name()); return (false); } } if (l_blocksize == 0) { if (l_device_type == T_CDROM) l_blocksize = DEFAULT_CD_BLOCKSIZE; else l_blocksize = DEFAULT_BLOCKSIZE; } else if (l_blocksize < 0) { log_warnx("invalid blocksize %d for lun \"%s\"; " "must be larger than 0", l_blocksize, name()); return (false); } if (l_size != 0 && (l_size % l_blocksize) != 0) { log_warnx("invalid size for lun \"%s\"; " "must be multiple of blocksize", name()); return (false); } return (true); } bool conf::verify() { if (conf_pidfile_path.empty()) conf_pidfile_path = DEFAULT_PIDFILE; std::unordered_map path_map; for (const auto &kv : conf_luns) { struct lun *lun = kv.second.get(); if (!lun->verify()) return (false); const std::string &path = lun->path(); if (path.empty()) continue; const auto &pair = path_map.try_emplace(path, lun); if (!pair.second) { struct lun *lun2 = pair.first->second; log_debugx("WARNING: path \"%s\" duplicated " "between lun \"%s\", and " "lun \"%s\"", path.c_str(), lun->name(), lun2->name()); } } for (auto &kv : conf_targets) { kv.second->verify(); } for (auto &kv : conf_portal_groups) { kv.second->verify(this); } for (const auto &kv : conf_auth_groups) { const std::string &ag_name = kv.first; if (ag_name == "default" || ag_name == "no-authentication" || ag_name == "no-access") continue; if (kv.second.use_count() == 1) { log_warnx("auth-group \"%s\" not assigned " "to any target", ag_name.c_str()); } } return (true); } bool portal::reuse_socket(struct portal &oldp) { struct kevent kev; if (p_listen != oldp.p_listen) return (false); if (!oldp.p_socket) return (false); EV_SET(&kev, oldp.p_socket, EVFILT_READ, EV_ADD, 0, 0, this); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1) return (false); p_socket = std::move(oldp.p_socket); return (true); } bool portal::init_socket() { struct portal_group *pg = portal_group(); struct kevent kev; freebsd::fd_up s; int error; int one = 1; #ifdef ICL_KERNEL_PROXY if (proxy_mode) { int id = pg->conf()->add_proxy_portal(this); log_debugx("listening on %s, %s \"%s\", " "portal id %d, using ICL proxy", listen(), pg->keyword(), pg->name(), id); kernel_listen(ai(), protocol() == ISER, id); return (true); } #endif assert(proxy_mode == false); assert(protocol() != portal_protocol::ISER); log_debugx("listening on %s, %s \"%s\"", listen(), pg->keyword(), pg->name()); s = ::socket(p_ai->ai_family, p_ai->ai_socktype, p_ai->ai_protocol); if (!s) { log_warn("socket(2) failed for %s", listen()); return (false); } if (setsockopt(s, SOL_SOCKET, SO_NO_DDP, &one, sizeof(one)) == -1) log_warn("setsockopt(SO_NO_DDP) failed for %s", listen()); error = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if (error != 0) { log_warn("setsockopt(SO_REUSEADDR) failed for %s", listen()); return (false); } if (pg->dscp() != -1) { /* Only allow the 6-bit DSCP field to be modified */ int tos = pg->dscp() << 2; switch (p_ai->ai_family) { case AF_INET: if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IP_TOS) failed for %s", listen()); break; case AF_INET6: if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IPV6_TCLASS) failed for %s", listen()); break; } } if (pg->pcp() != -1) { int pcp = pg->pcp(); switch (p_ai->ai_family) { case AF_INET: if (setsockopt(s, IPPROTO_IP, IP_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IP_VLAN_PCP) failed for %s", listen()); break; case AF_INET6: if (setsockopt(s, IPPROTO_IPV6, IPV6_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IPV6_VLAN_PCP) failed for %s", listen()); break; } } if (!init_socket_options(s)) return (false); error = bind(s, p_ai->ai_addr, p_ai->ai_addrlen); if (error != 0) { log_warn("bind(2) failed for %s", listen()); return (false); } error = ::listen(s, -1); if (error != 0) { log_warn("listen(2) failed for %s", listen()); return (false); } EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, this); error = kevent(kqfd, &kev, 1, NULL, 0, NULL); if (error == -1) { log_warn("kevent(2) failed to register for %s", listen()); return (false); } p_socket = std::move(s); return (true); } bool conf::reuse_portal_group_socket(struct portal &newp) { for (auto &kv : conf_portal_groups) { struct portal_group &pg = *kv.second; if (pg.reuse_socket(newp)) return (true); } return (false); } int conf::apply(struct conf *oldconf) { int cumulated_error = 0; if (oldconf->conf_debug != conf_debug) { log_debugx("changing debug level to %d", conf_debug); log_init(conf_debug); } /* * On startup, oldconf created via conf_new_from_kernel will * not contain a valid pidfile_path, and the current * conf_pidfile will already own the pidfile. On shutdown, * the temporary newconf will not contain a valid * pidfile_path, and the pidfile will be cleaned up when the * oldconf is deleted. */ if (!oldconf->conf_pidfile_path.empty() && !conf_pidfile_path.empty()) { if (oldconf->conf_pidfile_path != conf_pidfile_path) { /* pidfile has changed. rename it */ log_debugx("moving pidfile to %s", conf_pidfile_path.c_str()); if (rename(oldconf->conf_pidfile_path.c_str(), conf_pidfile_path.c_str()) != 0) { log_err(1, "renaming pidfile %s -> %s", oldconf->conf_pidfile_path.c_str(), conf_pidfile_path.c_str()); } } conf_pidfile = std::move(oldconf->conf_pidfile); } /* * Go through the new portal groups, assigning tags or preserving old. */ for (auto &kv : conf_portal_groups) { struct portal_group &newpg = *kv.second; if (newpg.tag() != 0) continue; auto it = oldconf->conf_portal_groups.find(kv.first); if (it != oldconf->conf_portal_groups.end()) newpg.set_tag(it->second->tag()); else newpg.allocate_tag(); } /* Deregister on removed iSNS servers. */ for (auto &kv : oldconf->conf_isns) { if (conf_isns.count(kv.first) == 0) oldconf->isns_deregister_targets(&kv.second); } /* * XXX: If target or lun removal fails, we should somehow "move" * the old lun or target into this, so that subsequent * conf::apply() would try to remove them again. That would * be somewhat hairy, though, and lun deletion failures don't * really happen, so leave it as it is for now. */ /* * First, remove any ports present in the old configuration * and missing in the new one. */ for (const auto &kv : oldconf->conf_ports) { const std::string &name = kv.first; port *oldport = kv.second.get(); if (oldport->is_dummy()) continue; const auto it = conf_ports.find(name); if (it != conf_ports.end() && !it->second->is_dummy()) continue; log_debugx("removing port \"%s\"", name.c_str()); if (!oldport->kernel_remove()) { log_warnx("failed to remove port %s", name.c_str()); /* * XXX: Uncomment after fixing the root cause. * * cumulated_error++; */ } } /* * Second, remove any LUNs present in the old configuration * and missing in the new one. */ for (auto it = oldconf->conf_luns.begin(); it != oldconf->conf_luns.end(); ) { struct lun *oldlun = it->second.get(); auto newit = conf_luns.find(it->first); if (newit == conf_luns.end()) { log_debugx("lun \"%s\", CTL lun %d " "not found in new configuration; " "removing", oldlun->name(), oldlun->ctl_lun()); if (!oldlun->kernel_remove()) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", oldlun->name(), oldlun->ctl_lun()); cumulated_error++; } it++; continue; } /* * Also remove the LUNs changed by more than size. */ struct lun *newlun = newit->second.get(); if (oldlun->changed(*newlun)) { if (!oldlun->kernel_remove()) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", oldlun->name(), oldlun->ctl_lun()); cumulated_error++; } /* * Delete the lun from the old configuration * so it is added as a new LUN below. */ it = oldconf->conf_luns.erase(it); continue; } newlun->set_ctl_lun(oldlun->ctl_lun()); it++; } for (auto it = conf_luns.begin(); it != conf_luns.end(); ) { struct lun *newlun = it->second.get(); auto oldit = oldconf->conf_luns.find(it->first); if (oldit != oldconf->conf_luns.end()) { log_debugx("modifying lun \"%s\", CTL lun %d", newlun->name(), newlun->ctl_lun()); if (!newlun->kernel_modify()) { log_warnx("failed to " "modify lun \"%s\", CTL lun %d", newlun->name(), newlun->ctl_lun()); cumulated_error++; } it++; continue; } log_debugx("adding lun \"%s\"", newlun->name()); if (!newlun->kernel_add()) { log_warnx("failed to add lun \"%s\"", newlun->name()); delete_target_luns(newlun); it = conf_luns.erase(it); cumulated_error++; } else it++; } /* * Now add new ports or modify existing ones. */ for (auto it = conf_ports.begin(); it != conf_ports.end(); ) { const std::string &name = it->first; port *newport = it->second.get(); if (newport->is_dummy()) { it++; continue; } const auto oldit = oldconf->conf_ports.find(name); if (oldit == oldconf->conf_ports.end() || oldit->second->is_dummy()) { log_debugx("adding port \"%s\"", name.c_str()); if (!newport->kernel_add()) { log_warnx("failed to add port %s", name.c_str()); /* * XXX: Uncomment after fixing the * root cause. * * cumulated_error++; */ /* * conf "owns" the port, but other * objects contain pointers to this * port that must be removed before * deleting the port. */ newport->clear_references(); it = conf_ports.erase(it); } else it++; } else { log_debugx("updating port \"%s\"", name.c_str()); if (!newport->kernel_update(oldit->second.get())) log_warnx("failed to update port %s", name.c_str()); it++; } } /* * Go through the new portals, opening the sockets as necessary. */ for (auto &kv : conf_portal_groups) { cumulated_error += kv.second->open_sockets(*oldconf); } /* * Go through the no longer used sockets, closing them. */ for (auto &kv : oldconf->conf_portal_groups) { kv.second->close_sockets(); } /* (Re-)Register on remaining/new iSNS servers. */ for (auto &kv : conf_isns) { auto it = oldconf->conf_isns.find(kv.first); if (it == oldconf->conf_isns.end()) isns_register_targets(&kv.second, nullptr); else isns_register_targets(&kv.second, oldconf); } isns_schedule_update(); return (cumulated_error); } bool timed_out(void) { return (sigalrm_received); } static void sigalrm_handler_fatal(int dummy __unused) { /* * It would be easiest to just log an error and exit. We can't * do this, though, because log_errx() is not signal safe, since * it calls syslog(3). Instead, set a flag checked by pdu_send() * and pdu_receive(), to call log_errx() there. Should they fail * to notice, we'll exit here one second later. */ if (sigalrm_received) { /* * Oh well. Just give up and quit. */ _exit(2); } sigalrm_received = true; } static void sigalrm_handler(int dummy __unused) { sigalrm_received = true; } void stop_timer() { struct itimerval itv; int error; log_debugx("session timeout disabled"); bzero(&itv, sizeof(itv)); error = setitimer(ITIMER_REAL, &itv, NULL); if (error != 0) log_err(1, "setitimer"); sigalrm_received = false; } void start_timer(int timeout, bool fatal) { struct sigaction sa; struct itimerval itv; int error; if (timeout <= 0) { stop_timer(); return; } sigalrm_received = false; bzero(&sa, sizeof(sa)); if (fatal) sa.sa_handler = sigalrm_handler_fatal; else sa.sa_handler = sigalrm_handler; sigfillset(&sa.sa_mask); error = sigaction(SIGALRM, &sa, NULL); if (error != 0) log_err(1, "sigaction"); /* * First SIGALRM will arive after timeout seconds. * If we do nothing, another one will arrive a second later. */ log_debugx("setting session timeout to %d seconds", timeout); bzero(&itv, sizeof(itv)); itv.it_interval.tv_sec = 1; itv.it_value.tv_sec = timeout; error = setitimer(ITIMER_REAL, &itv, NULL); if (error != 0) log_err(1, "setitimer"); } static int wait_for_children(bool block) { pid_t pid; int status; int num = 0; for (;;) { /* * If "block" is true, wait for at least one process. */ if (block && num == 0) pid = wait4(-1, &status, 0, NULL); else pid = wait4(-1, &status, WNOHANG, NULL); if (pid <= 0) break; if (WIFSIGNALED(status)) { log_warnx("child process %d terminated with signal %d", pid, WTERMSIG(status)); } else if (WEXITSTATUS(status) != 0) { log_warnx("child process %d terminated with exit status %d", pid, WEXITSTATUS(status)); } else { log_debugx("child process %d terminated gracefully", pid); } num++; } return (num); } static void -handle_connection(struct portal *portal, int fd, +handle_connection(struct portal *portal, freebsd::fd_up fd, const struct sockaddr *client_sa, bool dont_fork) { struct portal_group *pg; int error; pid_t pid; char host[NI_MAXHOST + 1]; struct conf *conf; pg = portal->portal_group(); conf = pg->conf(); if (dont_fork) { log_debugx("incoming connection; not forking due to -d flag"); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); while (conf->maxproc() > 0 && nchildren >= conf->maxproc()) { log_debugx("maxproc limit of %d child processes hit; " "waiting for child process to exit", conf->maxproc()); nchildren -= wait_for_children(true); assert(nchildren >= 0); } log_debugx("incoming connection; forking child process #%d", nchildren); nchildren++; pid = fork(); if (pid < 0) log_err(1, "fork"); - if (pid > 0) { - close(fd); + if (pid > 0) return; - } conf->close_pidfile(); } error = getnameinfo(client_sa, client_sa->sa_len, host, sizeof(host), NULL, 0, NI_NUMERICHOST); if (error != 0) log_errx(1, "getnameinfo: %s", gai_strerror(error)); log_debugx("accepted connection from %s; portal group \"%s\"", host, pg->name()); log_set_peer_addr(host); setproctitle("%s", host); - portal->handle_connection(fd, host, client_sa); + portal->handle_connection(std::move(fd), host, client_sa); log_debugx("nothing more to do; exiting"); exit(0); } static void main_loop(bool dont_fork) { struct kevent kev; struct portal *portal; struct sockaddr_storage client_sa; socklen_t client_salen; #ifdef ICL_KERNEL_PROXY int connection_id; int portal_id; #endif int error, client_fd; for (;;) { if (sighup_received || sigterm_received || timed_out()) return; #ifdef ICL_KERNEL_PROXY if (proxy_mode) { client_salen = sizeof(client_sa); kernel_accept(&connection_id, &portal_id, (struct sockaddr *)&client_sa, &client_salen); assert(client_salen >= client_sa.ss_len); log_debugx("incoming connection, id %d, portal id %d", connection_id, portal_id); portal = conf->proxy_portal(portal_id); if (portal == nullptr) log_errx(1, "kernel returned invalid portal_id %d", portal_id); handle_connection(portal, connection_id, (struct sockaddr *)&client_sa, dont_fork); } else { #endif assert(proxy_mode == false); error = kevent(kqfd, NULL, 0, &kev, 1, NULL); if (error == -1) { if (errno == EINTR) continue; log_err(1, "kevent"); } switch (kev.filter) { case EVFILT_READ: portal = reinterpret_cast(kev.udata); assert(portal->socket() == (int)kev.ident); client_salen = sizeof(client_sa); client_fd = accept(portal->socket(), (struct sockaddr *)&client_sa, &client_salen); if (client_fd < 0) { if (errno == ECONNABORTED) continue; log_err(1, "accept"); } assert(client_salen >= client_sa.ss_len); handle_connection(portal, client_fd, (struct sockaddr *)&client_sa, dont_fork); break; default: __assert_unreachable(); } #ifdef ICL_KERNEL_PROXY } #endif } } static void sighup_handler(int dummy __unused) { sighup_received = true; } static void sigterm_handler(int dummy __unused) { sigterm_received = true; } static void sigchld_handler(int dummy __unused) { /* * The only purpose of this handler is to make SIGCHLD * interrupt the ISCSIDWAIT ioctl(2), so we can call * wait_for_children(). */ } static void register_signals(void) { struct sigaction sa; int error; bzero(&sa, sizeof(sa)); sa.sa_handler = sighup_handler; sigfillset(&sa.sa_mask); error = sigaction(SIGHUP, &sa, NULL); if (error != 0) log_err(1, "sigaction"); sa.sa_handler = sigterm_handler; error = sigaction(SIGTERM, &sa, NULL); if (error != 0) log_err(1, "sigaction"); sa.sa_handler = sigterm_handler; error = sigaction(SIGINT, &sa, NULL); if (error != 0) log_err(1, "sigaction"); sa.sa_handler = sigchld_handler; error = sigaction(SIGCHLD, &sa, NULL); if (error != 0) log_err(1, "sigaction"); } static void check_perms(const char *path) { struct stat sb; int error; error = stat(path, &sb); if (error != 0) { log_warn("stat"); return; } if (sb.st_mode & S_IWOTH) { log_warnx("%s is world-writable", path); } else if (sb.st_mode & S_IROTH) { log_warnx("%s is world-readable", path); } else if (sb.st_mode & S_IXOTH) { /* * Ok, this one doesn't matter, but still do it, * just for consistency. */ log_warnx("%s is world-executable", path); } /* * XXX: Should we also check for owner != 0? */ } static conf_up conf_new_from_file(const char *path, bool ucl) { struct auth_group *ag; struct portal_group *pg; bool valid; log_debugx("obtaining configuration from %s", path); conf_up conf = std::make_unique(); ag = conf->add_auth_group("default"); assert(ag != NULL); ag = conf->add_auth_group("no-authentication"); assert(ag != NULL); ag->set_type(auth_type::NO_AUTHENTICATION); ag = conf->add_auth_group("no-access"); assert(ag != NULL); ag->set_type(auth_type::DENY); pg = conf->add_portal_group("default"); assert(pg != NULL); conf_start(conf.get()); if (ucl) valid = uclparse_conf(path); else valid = parse_conf(path); conf_finish(); if (!valid) { conf.reset(); return {}; } check_perms(path); if (!conf->default_auth_group_defined()) { log_debugx("auth-group \"default\" not defined; " "going with defaults"); ag = conf->find_auth_group("default").get(); assert(ag != NULL); ag->set_type(auth_type::DENY); } if (!conf->default_portal_group_defined()) { log_debugx("portal-group \"default\" not defined; " "going with defaults"); pg = conf->find_portal_group("default"); assert(pg != NULL); pg->add_default_portals(); } if (!conf->verify()) { conf.reset(); return {}; } return (conf); } /* * If the config file specifies physical ports for any target, associate them * with the config file. If necessary, create them. */ bool conf::add_pports(struct kports &kports) { struct pport *pp; int ret, i_pp, i_vp; for (auto &kv : conf_targets) { struct target *targ = kv.second.get(); if (!targ->has_pport()) continue; ret = sscanf(targ->pport(), "ioctl/%d/%d", &i_pp, &i_vp); if (ret > 0) { if (!add_port(kports, targ, i_pp, i_vp)) { log_warnx("can't create new ioctl port " "for %s", targ->label()); return (false); } continue; } pp = kports.find_port(targ->pport()); if (pp == NULL) { log_warnx("unknown port \"%s\" for %s", targ->pport(), targ->label()); return (false); } if (pp->linked()) { log_warnx("can't link port \"%s\" to %s, " "port already linked to some target", targ->pport(), targ->label()); return (false); } if (!add_port(targ, pp)) { log_warnx("can't link port \"%s\" to %s", targ->pport(), targ->label()); return (false); } } return (true); } int main(int argc, char **argv) { struct kports kports; const char *config_path = DEFAULT_CONFIG_PATH; int debug = 0, ch, error; bool daemonize = true; bool test_config = false; bool use_ucl = false; while ((ch = getopt(argc, argv, "dtuf:R")) != -1) { switch (ch) { case 'd': daemonize = false; debug++; break; case 't': test_config = true; break; case 'u': use_ucl = true; break; case 'f': config_path = optarg; break; case 'R': #ifndef ICL_KERNEL_PROXY log_errx(1, "ctld(8) compiled without ICL_KERNEL_PROXY " "does not support iSER protocol"); #endif proxy_mode = true; break; case '?': default: usage(); } } argc -= optind; if (argc != 0) usage(); log_init(debug); kernel_init(); conf_up newconf = conf_new_from_file(config_path, use_ucl); if (newconf == NULL) log_errx(1, "configuration error; exiting"); if (test_config) return (0); newconf->open_pidfile(); register_signals(); conf_up oldconf = conf_new_from_kernel(kports); if (debug > 0) { oldconf->set_debug(debug); newconf->set_debug(debug); } if (!newconf->add_pports(kports)) log_errx(1, "Error associating physical ports; exiting"); if (daemonize) { log_debugx("daemonizing"); if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); return (1); } } kqfd = kqueue(); if (kqfd == -1) { log_warn("Cannot create kqueue"); return (1); } error = newconf->apply(oldconf.get()); if (error != 0) log_errx(1, "failed to apply configuration; exiting"); oldconf.reset(); newconf->write_pidfile(); newconf->isns_schedule_update(); for (;;) { main_loop(!daemonize); if (sighup_received) { sighup_received = false; log_debugx("received SIGHUP, reloading configuration"); conf_up tmpconf = conf_new_from_file(config_path, use_ucl); if (tmpconf == NULL) { log_warnx("configuration error, " "continuing with old configuration"); } else if (!tmpconf->add_pports(kports)) { log_warnx("Error associating physical ports, " "continuing with old configuration"); } else { if (debug > 0) tmpconf->set_debug(debug); oldconf = std::move(newconf); newconf = std::move(tmpconf); error = newconf->apply(oldconf.get()); if (error != 0) log_warnx("failed to reload " "configuration"); oldconf.reset(); } } else if (sigterm_received) { log_debugx("exiting on signal; " "reloading empty configuration"); log_debugx("removing CTL iSCSI ports " "and terminating all connections"); oldconf = std::move(newconf); newconf = std::make_unique(); if (debug > 0) newconf->set_debug(debug); error = newconf->apply(oldconf.get()); if (error != 0) log_warnx("failed to apply configuration"); oldconf.reset(); log_warnx("exiting on signal"); return (0); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); if (timed_out()) { newconf->isns_update(); } } } /* NOTREACHED */ } diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh index 7842f1bb5715..6ecee3b73c4f 100644 --- a/usr.sbin/ctld/ctld.hh +++ b/usr.sbin/ctld/ctld.hh @@ -1,600 +1,600 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef __CTLD_HH__ #define __CTLD_HH__ #include #include #ifdef ICL_KERNEL_PROXY #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_CONFIG_PATH "/etc/ctl.conf" #define DEFAULT_PIDFILE "/var/run/ctld.pid" #define DEFAULT_BLOCKSIZE 512 #define DEFAULT_CD_BLOCKSIZE 2048 #define MAX_LUNS 1024 struct isns_req; struct port; struct auth { auth(std::string_view secret) : a_secret(secret) {} auth(std::string_view secret, std::string_view mutual_user, std::string_view mutual_secret) : a_secret(secret), a_mutual_user(mutual_user), a_mutual_secret(mutual_secret) {} bool mutual() const { return !a_mutual_user.empty(); } const char *secret() const { return a_secret.c_str(); } const char *mutual_user() const { return a_mutual_user.c_str(); } const char *mutual_secret() const { return a_mutual_secret.c_str(); } private: std::string a_secret; std::string a_mutual_user; std::string a_mutual_secret; }; struct auth_portal { bool matches(const struct sockaddr *sa) const; bool parse(const char *portal); private: struct sockaddr_storage ap_sa; int ap_mask = 0; }; enum class auth_type { UNKNOWN, DENY, NO_AUTHENTICATION, CHAP, CHAP_MUTUAL }; struct auth_group { auth_group(std::string label) : ag_label(label) {} auth_type type() const { return ag_type; } bool set_type(const char *str); void set_type(auth_type type); const char *label() const { return ag_label.c_str(); } bool add_chap(const char *user, const char *secret); bool add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2); const struct auth *find_auth(std::string_view user) const; bool add_initiator_name(std::string_view initiator_name); bool initiator_permitted(std::string_view initiator_name) const; bool add_initiator_portal(const char *initiator_portal); bool initiator_permitted(const struct sockaddr *sa) const; private: void check_secret_length(const char *user, const char *secret, const char *secret_type); std::string ag_label; auth_type ag_type = auth_type::UNKNOWN; std::unordered_map ag_auths; std::unordered_set ag_initiator_names; std::list ag_initiator_portals; }; using auth_group_sp = std::shared_ptr; enum class portal_protocol { ISCSI, ISER }; struct portal { portal(struct portal_group *pg, std::string_view listen, portal_protocol protocol, freebsd::addrinfo_up ai) : p_portal_group(pg), p_listen(listen), p_ai(std::move(ai)), p_protocol(protocol) {} virtual ~portal() = default; bool reuse_socket(portal &oldp); bool init_socket(); virtual bool init_socket_options(int s __unused) { return true; } - virtual void handle_connection(int fd, const char *host, + virtual void handle_connection(freebsd::fd_up fd, const char *host, const struct sockaddr *client_sa) = 0; portal_group *portal_group() { return p_portal_group; } const char *listen() const { return p_listen.c_str(); } const addrinfo *ai() const { return p_ai.get(); } portal_protocol protocol() const { return p_protocol; } int socket() const { return p_socket; } void close() { p_socket.reset(); } private: struct portal_group *p_portal_group; std::string p_listen; freebsd::addrinfo_up p_ai; portal_protocol p_protocol; freebsd::fd_up p_socket; }; using portal_up = std::unique_ptr; using port_up = std::unique_ptr; enum class discovery_filter { UNKNOWN, NONE, PORTAL, PORTAL_NAME, PORTAL_NAME_AUTH }; struct portal_group { portal_group(struct conf *conf, std::string_view name); virtual ~portal_group() = default; struct conf *conf() const { return pg_conf; } virtual const char *keyword() const = 0; const char *name() const { return pg_name.c_str(); } bool assigned() const { return pg_assigned; } bool is_dummy() const; bool is_redirecting() const { return !pg_redirection.empty(); } struct auth_group *discovery_auth_group() const { return pg_discovery_auth_group.get(); } discovery_filter discovery_filter() const { return pg_discovery_filter; } int dscp() const { return pg_dscp; } const char *offload() const { return pg_offload.c_str(); } const char *redirection() const { return pg_redirection.c_str(); } int pcp() const { return pg_pcp; } uint16_t tag() const { return pg_tag; } freebsd::nvlist_up options() const; const std::list &portals() const { return pg_portals; } const std::unordered_map &ports() const { return pg_ports; } virtual void allocate_tag() = 0; virtual bool add_portal(const char *value, portal_protocol protocol) = 0; virtual void add_default_portals() = 0; bool add_option(const char *name, const char *value); bool set_discovery_auth_group(const char *name); bool set_dscp(u_int dscp); virtual bool set_filter(const char *str) = 0; void set_foreign(); bool set_offload(const char *offload); bool set_pcp(u_int pcp); bool set_redirection(const char *addr); void set_tag(uint16_t tag); virtual port_up create_port(struct target *target, auth_group_sp ag) = 0; virtual port_up create_port(struct target *target, uint32_t ctl_port) = 0; void add_port(struct portal_group_port *port); const struct port *find_port(std::string_view target) const; void remove_port(struct portal_group_port *port); void verify(struct conf *conf); bool reuse_socket(struct portal &newp); int open_sockets(struct conf &oldconf); void close_sockets(); protected: struct conf *pg_conf; freebsd::nvlist_up pg_options; const char *pg_keyword; std::string pg_name; auth_group_sp pg_discovery_auth_group; enum discovery_filter pg_discovery_filter = discovery_filter::UNKNOWN; bool pg_foreign = false; bool pg_assigned = false; std::list pg_portals; std::unordered_map pg_ports; std::string pg_offload; std::string pg_redirection; int pg_dscp = -1; int pg_pcp = -1; uint16_t pg_tag = 0; }; using portal_group_up = std::unique_ptr; struct port { port(struct target *target); virtual ~port() = default; struct target *target() const { return p_target; } virtual struct auth_group *auth_group() const { return nullptr; } virtual struct portal_group *portal_group() const { return nullptr; } virtual bool is_dummy() const { return true; } virtual void clear_references(); bool kernel_add(); bool kernel_update(const port *oport); bool kernel_remove(); virtual bool kernel_create_port() = 0; virtual bool kernel_remove_port() = 0; protected: struct target *p_target; uint32_t p_ctl_port = 0; }; struct portal_group_port : public port { portal_group_port(struct target *target, struct portal_group *pg, auth_group_sp ag); portal_group_port(struct target *target, struct portal_group *pg, uint32_t ctl_port); ~portal_group_port() override = default; struct auth_group *auth_group() const override { return p_auth_group.get(); } struct portal_group *portal_group() const override { return p_portal_group; } bool is_dummy() const override; void clear_references() override; protected: auth_group_sp p_auth_group; struct portal_group *p_portal_group; }; struct ioctl_port final : public port { ioctl_port(struct target *target, int pp, int vp) : port(target), p_ioctl_pp(pp), p_ioctl_vp(vp) {} ~ioctl_port() override = default; bool kernel_create_port() override; bool kernel_remove_port() override; private: int p_ioctl_pp; int p_ioctl_vp; }; struct kernel_port final : public port { kernel_port(struct target *target, struct pport *pp) : port(target), p_pport(pp) {} ~kernel_port() override = default; bool kernel_create_port() override; bool kernel_remove_port() override; private: struct pport *p_pport; }; struct lun { lun(struct conf *conf, std::string_view name); const char *name() const { return l_name.c_str(); } const std::string &path() const { return l_path; } int ctl_lun() const { return l_ctl_lun; } freebsd::nvlist_up options() const; bool add_option(const char *name, const char *value); bool set_backend(std::string_view value); bool set_blocksize(size_t value); bool set_ctl_lun(uint32_t value); bool set_device_type(uint8_t device_type); bool set_device_type(const char *value); bool set_device_id(std::string_view value); bool set_path(std::string_view value); void set_scsiname(std::string_view value); bool set_serial(std::string_view value); bool set_size(uint64_t value); bool changed(const struct lun &old) const; bool verify(); bool kernel_add(); bool kernel_modify() const; bool kernel_remove() const; private: struct conf *l_conf; freebsd::nvlist_up l_options; std::string l_name; std::string l_backend; uint8_t l_device_type = 0; int l_blocksize = 0; std::string l_device_id; std::string l_path; std::string l_scsiname; std::string l_serial; uint64_t l_size = 0; int l_ctl_lun = -1; }; struct target { target(struct conf *conf, const char *keyword, std::string_view name); virtual ~target() = default; bool has_alias() const { return !t_alias.empty(); } bool has_pport() const { return !t_pport.empty(); } bool has_redirection() const { return !t_redirection.empty(); } const char *alias() const { return t_alias.c_str(); } const char *name() const { return t_name.c_str(); } const char *label() const { return t_label.c_str(); } const char *pport() const { return t_pport.c_str(); } bool private_auth() const { return t_private_auth; } const char *redirection() const { return t_redirection.c_str(); } struct auth_group *auth_group() const { return t_auth_group.get(); } const std::list &ports() const { return t_ports; } const struct lun *lun(int idx) const { return t_luns[idx]; } bool add_chap(const char *user, const char *secret); bool add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2); virtual bool add_initiator_name(std::string_view) { return false; } virtual bool add_initiator_portal(const char *) { return false; } virtual bool add_lun(u_int, const char *) { return false; } virtual bool add_portal_group(const char *pg_name, const char *ag_name) = 0; bool set_alias(std::string_view alias); bool set_auth_group(const char *ag_name); bool set_auth_type(const char *type); bool set_physical_port(std::string_view pport); bool set_redirection(const char *addr); virtual struct lun *start_lun(u_int) { return nullptr; } void add_port(struct port *port); void remove_lun(struct lun *lun); void remove_port(struct port *port); void verify(); protected: bool use_private_auth(const char *keyword); bool add_lun(u_int id, const char *lun_label, const char *lun_name); struct lun *start_lun(u_int id, const char *lun_label, const char *lun_name); virtual struct portal_group *default_portal_group() = 0; struct conf *t_conf; std::array t_luns; auth_group_sp t_auth_group; std::list t_ports; std::string t_name; std::string t_label; std::string t_alias; std::string t_redirection; /* Name of this target's physical port, if any, i.e. "isp0" */ std::string t_pport; bool t_private_auth; }; using target_up = std::unique_ptr; struct isns { isns(std::string_view addr, freebsd::addrinfo_up ai) : i_addr(addr), i_ai(std::move(ai)) {} const char *addr() const { return i_addr.c_str(); } freebsd::fd_up connect(); bool send_request(int s, struct isns_req req); private: std::string i_addr; freebsd::addrinfo_up i_ai; }; struct conf { int maxproc() const { return conf_maxproc; } int timeout() const { return conf_timeout; } bool default_auth_group_defined() const { return conf_default_ag_defined; } bool default_portal_group_defined() const { return conf_default_pg_defined; } struct auth_group *add_auth_group(const char *ag_name); struct auth_group *define_default_auth_group(); auth_group_sp find_auth_group(std::string_view ag_name); struct portal_group *add_portal_group(const char *name); struct portal_group *define_default_portal_group(); struct portal_group *find_portal_group(std::string_view name); bool add_port(struct target *target, struct portal_group *pg, auth_group_sp ag); bool add_port(struct target *target, struct portal_group *pg, uint32_t ctl_port); bool add_port(struct target *target, struct pport *pp); bool add_port(struct kports &kports, struct target *target, int pp, int vp); bool add_pports(struct kports &kports); struct target *add_target(const char *name); struct target *find_target(std::string_view name); struct lun *add_lun(const char *name); struct lun *find_lun(std::string_view name); void set_debug(int debug); void set_isns_period(int period); void set_isns_timeout(int timeout); void set_maxproc(int maxproc); bool set_pidfile_path(std::string_view path); void set_timeout(int timeout); void open_pidfile(); void write_pidfile(); void close_pidfile(); bool add_isns(const char *addr); void isns_register_targets(struct isns *isns, struct conf *oldconf); void isns_deregister_targets(struct isns *isns); void isns_schedule_update(); void isns_update(); int apply(struct conf *oldconf); void delete_target_luns(struct lun *lun); bool reuse_portal_group_socket(struct portal &newp); bool verify(); private: struct isns_req isns_register_request(const char *hostname); struct isns_req isns_check_request(const char *hostname); struct isns_req isns_deregister_request(const char *hostname); void isns_check(struct isns *isns); std::string conf_pidfile_path; std::unordered_map> conf_luns; std::unordered_map> conf_targets; std::unordered_map conf_auth_groups; std::unordered_map> conf_ports; std::unordered_map conf_portal_groups; std::unordered_map conf_isns; struct target *conf_first_target = nullptr; int conf_isns_period = 900; int conf_isns_timeout = 5; int conf_debug = 0; int conf_timeout = 60; int conf_maxproc = 30; freebsd::pidfile conf_pidfile; bool conf_default_pg_defined = false; bool conf_default_ag_defined = false; #ifdef ICL_KERNEL_PROXY public: int add_proxy_portal(portal *); portal *proxy_portal(int); private: std::vector conf_proxy_portals; #endif }; using conf_up = std::unique_ptr; /* Physical ports exposed by the kernel */ struct pport { pport(std::string_view name, uint32_t ctl_port) : pp_name(name), pp_ctl_port(ctl_port) {} const char *name() const { return pp_name.c_str(); } uint32_t ctl_port() const { return pp_ctl_port; } bool linked() const { return pp_linked; } void link() { pp_linked = true; } private: std::string pp_name; uint32_t pp_ctl_port; bool pp_linked; }; struct kports { bool add_port(std::string &name, uint32_t ctl_port); bool has_port(std::string_view name); struct pport *find_port(std::string_view name); private: std::unordered_map pports; }; extern bool proxy_mode; extern int ctl_fd; bool parse_conf(const char *path); bool uclparse_conf(const char *path); conf_up conf_new_from_kernel(struct kports &kports); void conf_finish(void); void conf_start(struct conf *new_conf); bool option_new(nvlist_t *nvl, const char *name, const char *value); freebsd::addrinfo_up parse_addr_port(const char *address, const char *def_port); void kernel_init(void); void kernel_capsicate(void); #ifdef ICL_KERNEL_PROXY void kernel_listen(struct addrinfo *ai, bool iser, int portal_id); void kernel_accept(int *connection_id, int *portal_id, struct sockaddr *client_sa, socklen_t *client_salen); void kernel_send(struct pdu *pdu); void kernel_receive(struct pdu *pdu); #endif bool ctl_create_port(const char *driver, const nvlist_t *nvl, uint32_t *ctl_port); bool ctl_remove_port(const char *driver, nvlist_t *nvl); portal_group_up iscsi_make_portal_group(struct conf *conf, std::string_view name); target_up iscsi_make_target(struct conf *conf, std::string_view name); void start_timer(int timeout, bool fatal = false); void stop_timer(); bool timed_out(); #endif /* !__CTLD_HH__ */ diff --git a/usr.sbin/ctld/iscsi.cc b/usr.sbin/ctld/iscsi.cc index 8d32974ab504..bee036b95bf2 100644 --- a/usr.sbin/ctld/iscsi.cc +++ b/usr.sbin/ctld/iscsi.cc @@ -1,508 +1,508 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2003, 2004 Silicon Graphics International Corp. * Copyright (c) 1997-2007 Kenneth D. Merry * Copyright (c) 2012 The FreeBSD Foundation * Copyright (c) 2017 Jakub Wojciech Klama * All rights reserved. * Copyright (c) 2025 Chelsio Communications, Inc. * * Portions of this software were developed by Edward Tomasz Napierala * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * */ #include #include #include #include #include #include #include #include #include #include #include #include "ctld.hh" #include "iscsi.hh" #define SOCKBUF_SIZE 1048576 struct iscsi_portal final : public portal { iscsi_portal(struct portal_group *pg, const char *listen, portal_protocol protocol, freebsd::addrinfo_up ai) : portal(pg, listen, protocol, std::move(ai)) {} bool init_socket_options(int s) override; - void handle_connection(int fd, const char *host, + void handle_connection(freebsd::fd_up fd, const char *host, const struct sockaddr *client_sa) override; }; struct iscsi_portal_group final : public portal_group { iscsi_portal_group(struct conf *conf, std::string_view name) : portal_group(conf, name) {} const char *keyword() const override { return "portal-group"; } void allocate_tag() override; bool add_portal(const char *value, portal_protocol protocol) override; void add_default_portals() override; bool set_filter(const char *str) override; virtual port_up create_port(struct target *target, auth_group_sp ag) override; virtual port_up create_port(struct target *target, uint32_t ctl_port) override; private: static uint16_t last_portal_group_tag; }; struct iscsi_port final : public portal_group_port { iscsi_port(struct target *target, struct portal_group *pg, auth_group_sp ag) : portal_group_port(target, pg, ag) {} iscsi_port(struct target *target, struct portal_group *pg, uint32_t ctl_port) : portal_group_port(target, pg, ctl_port) {} bool kernel_create_port() override; bool kernel_remove_port() override; private: static bool module_loaded; static void load_kernel_module(); }; struct iscsi_target final : public target { iscsi_target(struct conf *conf, std::string_view name) : target(conf, "target", name) {} bool add_initiator_name(std::string_view name) override; bool add_initiator_portal(const char *addr) override; bool add_lun(u_int id, const char *lun_name) override; bool add_portal_group(const char *pg_name, const char *ag_name) override; struct lun *start_lun(u_int id) override; protected: struct portal_group *default_portal_group() override; }; #ifdef ICL_KERNEL_PROXY static void pdu_receive_proxy(struct pdu *pdu); static void pdu_send_proxy(struct pdu *pdu); #endif /* ICL_KERNEL_PROXY */ static void pdu_fail(const struct connection *conn, const char *reason); uint16_t iscsi_portal_group::last_portal_group_tag = 0xff; bool iscsi_port::module_loaded = false; static struct connection_ops conn_ops = { .timed_out = timed_out, #ifdef ICL_KERNEL_PROXY .pdu_receive_proxy = pdu_receive_proxy, .pdu_send_proxy = pdu_send_proxy, #else .pdu_receive_proxy = nullptr, .pdu_send_proxy = nullptr, #endif .fail = pdu_fail, }; portal_group_up iscsi_make_portal_group(struct conf *conf, std::string_view name) { return std::make_unique(conf, name); } target_up iscsi_make_target(struct conf *conf, std::string_view name) { return std::make_unique(conf, name); } void iscsi_portal_group::allocate_tag() { set_tag(++last_portal_group_tag); } bool iscsi_portal_group::add_portal(const char *value, portal_protocol protocol) { switch (protocol) { case portal_protocol::ISCSI: case portal_protocol::ISER: break; default: log_warnx("unsupported portal protocol for %s", value); return (false); } freebsd::addrinfo_up ai = parse_addr_port(value, "3260"); if (!ai) { log_warnx("invalid listen address %s", value); return (false); } /* * XXX: getaddrinfo(3) may return multiple addresses; we should turn * those into multiple portals. */ pg_portals.emplace_back(std::make_unique(this, value, protocol, std::move(ai))); return (true); } void iscsi_portal_group::add_default_portals() { add_portal("0.0.0.0", portal_protocol::ISCSI); add_portal("[::]", portal_protocol::ISCSI); } bool iscsi_portal_group::set_filter(const char *str) { enum discovery_filter filter; if (strcmp(str, "none") == 0) { filter = discovery_filter::NONE; } else if (strcmp(str, "portal") == 0) { filter = discovery_filter::PORTAL; } else if (strcmp(str, "portal-name") == 0) { filter = discovery_filter::PORTAL_NAME; } else if (strcmp(str, "portal-name-auth") == 0) { filter = discovery_filter::PORTAL_NAME_AUTH; } else { log_warnx("invalid discovery-filter \"%s\" for portal-group " "\"%s\"; valid values are \"none\", \"portal\", " "\"portal-name\", and \"portal-name-auth\"", str, name()); return (false); } if (pg_discovery_filter != discovery_filter::UNKNOWN && pg_discovery_filter != filter) { log_warnx("cannot set discovery-filter to \"%s\" for " "portal-group \"%s\"; already has a different " "value", str, name()); return (false); } pg_discovery_filter = filter; return (true); } port_up iscsi_portal_group::create_port(struct target *target, auth_group_sp ag) { return std::make_unique(target, this, ag); } port_up iscsi_portal_group::create_port(struct target *target, uint32_t ctl_port) { return std::make_unique(target, this, ctl_port); } void iscsi_port::load_kernel_module() { int saved_errno; if (module_loaded) return; saved_errno = errno; if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1) log_warn("couldn't load cfiscsi"); errno = saved_errno; module_loaded = true; } bool iscsi_port::kernel_create_port() { struct portal_group *pg = p_portal_group; struct target *targ = p_target; load_kernel_module(); freebsd::nvlist_up nvl = pg->options(); nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name()); nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name()); nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u", pg->tag()); if (targ->has_alias()) { nvlist_add_string(nvl.get(), "cfiscsi_target_alias", targ->alias()); } return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port)); } bool iscsi_port::kernel_remove_port() { freebsd::nvlist_up nvl(nvlist_create(0)); nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name()); nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u", p_portal_group->tag()); return (ctl_remove_port("iscsi", nvl.get())); } bool iscsi_portal::init_socket_options(int s) { int sockbuf; sockbuf = SOCKBUF_SIZE; if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf, sizeof(sockbuf)) == -1) { log_warn("setsockopt(SO_RCVBUF) failed for %s", listen()); return (false); } sockbuf = SOCKBUF_SIZE; if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf, sizeof(sockbuf)) == -1) { log_warn("setsockopt(SO_SNDBUF) failed for %s", listen()); return (false); } return (true); } bool iscsi_target::add_initiator_name(std::string_view name) { if (!use_private_auth("initiator-name")) return (false); return (t_auth_group->add_initiator_name(name)); } bool iscsi_target::add_initiator_portal(const char *addr) { if (!use_private_auth("initiator-portal")) return (false); return (t_auth_group->add_initiator_portal(addr)); } bool iscsi_target::add_lun(u_int id, const char *lun_name) { std::string lun_label = "LUN " + std::to_string(id); return target::add_lun(id, lun_label.c_str(), lun_name); } bool iscsi_target::add_portal_group(const char *pg_name, const char *ag_name) { struct portal_group *pg; auth_group_sp ag; pg = t_conf->find_portal_group(pg_name); if (pg == NULL) { log_warnx("unknown portal-group \"%s\" for %s", pg_name, label()); return (false); } if (ag_name != NULL) { ag = t_conf->find_auth_group(ag_name); if (ag == NULL) { log_warnx("unknown auth-group \"%s\" for %s", ag_name, label()); return (false); } } if (!t_conf->add_port(this, pg, std::move(ag))) { log_warnx("can't link portal-group \"%s\" to %s", pg_name, label()); return (false); } return (true); } struct lun * iscsi_target::start_lun(u_int id) { std::string lun_label = "LUN " + std::to_string(id); std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id); return target::start_lun(id, lun_label.c_str(), lun_name.c_str()); } struct portal_group * iscsi_target::default_portal_group() { return t_conf->find_portal_group("default"); } #ifdef ICL_KERNEL_PROXY static void pdu_receive_proxy(struct pdu *pdu) { struct connection *conn; size_t len; assert(proxy_mode); conn = pdu->pdu_connection; kernel_receive(pdu); len = pdu_ahs_length(pdu); if (len > 0) log_errx(1, "protocol error: non-empty AHS"); len = pdu_data_segment_length(pdu); assert(len <= (size_t)conn->conn_max_recv_data_segment_length); pdu->pdu_data_len = len; } static void pdu_send_proxy(struct pdu *pdu) { assert(proxy_mode); pdu_set_data_segment_length(pdu, pdu->pdu_data_len); kernel_send(pdu); } #endif /* ICL_KERNEL_PROXY */ static void pdu_fail(const struct connection *conn __unused, const char *reason __unused) { } -iscsi_connection::iscsi_connection(struct portal *portal, int fd, +iscsi_connection::iscsi_connection(struct portal *portal, freebsd::fd_up fd, const char *host, const struct sockaddr *client_sa) : - conn_portal(portal), conn_initiator_addr(host), + conn_portal(portal), conn_fd(std::move(fd)), conn_initiator_addr(host), conn_initiator_sa(client_sa) { connection_init(&conn, &conn_ops, proxy_mode); - conn.conn_socket = fd; + conn.conn_socket = conn_fd; } iscsi_connection::~iscsi_connection() { chap_delete(conn_chap); } void iscsi_connection::kernel_handoff() { struct portal_group *pg = conn_portal->portal_group(); struct ctl_iscsi req; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_HANDOFF; strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(), sizeof(req.data.handoff.initiator_name)); strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(), sizeof(req.data.handoff.initiator_addr)); if (!conn_initiator_alias.empty()) { strlcpy(req.data.handoff.initiator_alias, conn_initiator_alias.c_str(), sizeof(req.data.handoff.initiator_alias)); } memcpy(req.data.handoff.initiator_isid, conn_initiator_isid, sizeof(req.data.handoff.initiator_isid)); strlcpy(req.data.handoff.target_name, conn_target->name(), sizeof(req.data.handoff.target_name)); strlcpy(req.data.handoff.offload, pg->offload(), sizeof(req.data.handoff.offload)); #ifdef ICL_KERNEL_PROXY if (proxy_mode) req.data.handoff.connection_id = conn.conn_socket; else req.data.handoff.socket = conn.conn_socket; #else req.data.handoff.socket = conn.conn_socket; #endif req.data.handoff.portal_group_tag = pg->tag(); if (conn.conn_header_digest == CONN_DIGEST_CRC32C) req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; if (conn.conn_data_digest == CONN_DIGEST_CRC32C) req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; req.data.handoff.cmdsn = conn.conn_cmdsn; req.data.handoff.statsn = conn.conn_statsn; req.data.handoff.max_recv_data_segment_length = conn.conn_max_recv_data_segment_length; req.data.handoff.max_send_data_segment_length = conn.conn_max_send_data_segment_length; req.data.handoff.max_burst_length = conn.conn_max_burst_length; req.data.handoff.first_burst_length = conn.conn_first_burst_length; req.data.handoff.immediate_data = conn.conn_immediate_data; if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { log_err(1, "error issuing CTL_ISCSI ioctl; " "dropping connection"); } if (req.status != CTL_ISCSI_OK) { log_errx(1, "error returned from CTL iSCSI handoff request: " "%s; dropping connection", req.error_str); } } void iscsi_connection::handle() { login(); if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { kernel_handoff(); log_debugx("connection handed off to the kernel"); } else { assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY); discovery(); } } void -iscsi_portal::handle_connection(int fd, const char *host, +iscsi_portal::handle_connection(freebsd::fd_up fd, const char *host, const struct sockaddr *client_sa) { struct conf *conf = portal_group()->conf(); - iscsi_connection conn(this, fd, host, client_sa); + iscsi_connection conn(this, std::move(fd), host, client_sa); start_timer(conf->timeout(), true); kernel_capsicate(); conn.handle(); } diff --git a/usr.sbin/ctld/iscsi.hh b/usr.sbin/ctld/iscsi.hh index 5a6729541119..d510e8c6731b 100644 --- a/usr.sbin/ctld/iscsi.hh +++ b/usr.sbin/ctld/iscsi.hh @@ -1,78 +1,79 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef __ISCSI_HH__ #define __ISCSI_HH__ #define CONN_SESSION_TYPE_NONE 0 #define CONN_SESSION_TYPE_DISCOVERY 1 #define CONN_SESSION_TYPE_NORMAL 2 struct iscsi_connection { - iscsi_connection(struct portal *portal, int fd, const char *host, - const struct sockaddr *client_sa); + iscsi_connection(struct portal *portal, freebsd::fd_up fd, + const char *host, const struct sockaddr *client_sa); ~iscsi_connection(); void handle(); private: void login(); void login_chap(struct auth_group *ag); void login_negotiate_key(struct pdu *request, const char *name, const char *value, bool skipped_security, struct keys *response_keys); bool login_portal_redirect(struct pdu *request); bool login_target_redirect(struct pdu *request); void login_negotiate(struct pdu *request); void login_wait_transition(); void discovery(); bool discovery_target_filtered_out(const struct port *port) const; void kernel_handoff(); struct connection conn; struct portal *conn_portal = nullptr; const struct port *conn_port = nullptr; struct target *conn_target = nullptr; + freebsd::fd_up conn_fd; int conn_session_type = CONN_SESSION_TYPE_NONE; std::string conn_initiator_name; std::string conn_initiator_addr; std::string conn_initiator_alias; uint8_t conn_initiator_isid[6]; const struct sockaddr *conn_initiator_sa = nullptr; int conn_max_recv_data_segment_limit = 0; int conn_max_send_data_segment_limit = 0; int conn_max_burst_limit = 0; int conn_first_burst_limit = 0; std::string conn_user; struct chap *conn_chap = nullptr; }; #endif /* !__ISCSI_HH__ */ diff --git a/usr.sbin/ctld/login.cc b/usr.sbin/ctld/login.cc index 87e8d3092fef..cda11cc1f21b 100644 --- a/usr.sbin/ctld/login.cc +++ b/usr.sbin/ctld/login.cc @@ -1,1108 +1,1108 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include "ctld.hh" #include "iscsi.hh" #include "iscsi_proto.h" #define MAX_DATA_SEGMENT_LENGTH (128 * 1024) static void login_send_error(struct pdu *request, char error_class, char detail); static void kernel_limits(const char *offload, int s, int *max_recv_dsl, int *max_send_dsl, int *max_burst_length, int *first_burst_length) { struct ctl_iscsi req; struct ctl_iscsi_limits_params *cilp; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_LIMITS; cilp = (struct ctl_iscsi_limits_params *)&(req.data.limits); strlcpy(cilp->offload, offload, sizeof(cilp->offload)); cilp->socket = s; if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { log_err(1, "error issuing CTL_ISCSI ioctl; " "dropping connection"); } if (req.status != CTL_ISCSI_OK) { log_errx(1, "error returned from CTL iSCSI limits request: " "%s; dropping connection", req.error_str); } if (cilp->max_recv_data_segment_length != 0) { *max_recv_dsl = cilp->max_recv_data_segment_length; *max_send_dsl = cilp->max_recv_data_segment_length; } if (cilp->max_send_data_segment_length != 0) *max_send_dsl = cilp->max_send_data_segment_length; if (cilp->max_burst_length != 0) *max_burst_length = cilp->max_burst_length; if (cilp->first_burst_length != 0) *first_burst_length = cilp->first_burst_length; if (*max_burst_length < *first_burst_length) *first_burst_length = *max_burst_length; if (offload[0] != '\0') { log_debugx("Kernel limits for offload \"%s\" are " "MaxRecvDataSegment=%d, max_send_dsl=%d, " "MaxBurstLength=%d, FirstBurstLength=%d", offload, *max_recv_dsl, *max_send_dsl, *max_burst_length, *first_burst_length); } else { log_debugx("Kernel limits are " "MaxRecvDataSegment=%d, max_send_dsl=%d, " "MaxBurstLength=%d, FirstBurstLength=%d", *max_recv_dsl, *max_send_dsl, *max_burst_length, *first_burst_length); } } static void login_set_nsg(struct pdu *response, int nsg) { struct iscsi_bhs_login_response *bhslr; assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr->bhslr_flags &= 0xFC; bhslr->bhslr_flags |= nsg; bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT; } static int login_csg(const struct pdu *request) { struct iscsi_bhs_login_request *bhslr; bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; return ((bhslr->bhslr_flags & 0x0C) >> 2); } static void login_set_csg(struct pdu *response, int csg) { struct iscsi_bhs_login_response *bhslr; assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || csg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr->bhslr_flags &= 0xF3; bhslr->bhslr_flags |= csg << 2; } static struct pdu * login_receive(struct connection *conn, bool initial) { struct pdu *request; struct iscsi_bhs_login_request *bhslr; request = pdu_new(conn); pdu_receive(request); if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != ISCSI_BHS_OPCODE_LOGIN_REQUEST) { /* * The first PDU in session is special - if we receive any PDU * different than login request, we have to drop the connection * without sending response ("A target receiving any PDU * except a Login request before the Login Phase is started MUST * immediately terminate the connection on which the PDU * was received.") */ if (initial == false) login_send_error(request, 0x02, 0x0b); log_errx(1, "protocol error: received invalid opcode 0x%x", request->pdu_bhs->bhs_opcode); } bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; /* * XXX: Implement the C flag some day. */ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { login_send_error(request, 0x03, 0x00); log_errx(1, "received Login PDU with unsupported \"C\" flag"); } if (bhslr->bhslr_version_max != 0x00) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with unsupported " "Version-max 0x%x", bhslr->bhslr_version_max); } if (bhslr->bhslr_version_min != 0x00) { login_send_error(request, 0x02, 0x05); log_errx(1, "received Login PDU with unsupported " "Version-min 0x%x", bhslr->bhslr_version_min); } if (initial == false && ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with decreasing CmdSN: " "was %u, is %u", conn->conn_cmdsn, ntohl(bhslr->bhslr_cmdsn)); } if (initial == false && ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with wrong ExpStatSN: " "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn), conn->conn_statsn); } conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); return (request); } static struct pdu * login_new_response(struct pdu *request) { struct pdu *response; struct connection *conn; struct iscsi_bhs_login_request *bhslr; struct iscsi_bhs_login_response *bhslr2; bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; conn = request->pdu_connection; response = pdu_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE; login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION); memcpy(bhslr2->bhslr_isid, bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid)); bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); return (response); } static void login_send_error(struct pdu *request, char error_class, char detail) { struct pdu *response; struct iscsi_bhs_login_response *bhslr2; log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; " "see next line for reason", error_class, detail); response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_status_class = error_class; bhslr2->bhslr_status_detail = detail; pdu_send(response); pdu_delete(response); } static int login_list_contains(const char *list, const char *what) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, what) == 0) { free(tofree); return (1); } } free(tofree); return (0); } static int login_list_prefers(const char *list, const char *choice1, const char *choice2) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, choice1) == 0) { free(tofree); return (1); } if (strcmp(token, choice2) == 0) { free(tofree); return (2); } } free(tofree); return (-1); } static struct pdu * login_receive_chap_a(struct connection *conn) { struct pdu *request; struct keys *request_keys; const char *chap_a; request = login_receive(conn, false); request_keys = keys_new(); keys_load_pdu(request_keys, request); chap_a = keys_find(request_keys, "CHAP_A"); if (chap_a == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_A"); } if (login_list_contains(chap_a, "5") == 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login PDU with unsupported CHAP_A " "\"%s\"", chap_a); } keys_delete(request_keys); return (request); } static void login_send_chap_c(struct pdu *request, struct chap *chap) { struct pdu *response; struct keys *response_keys; char *chap_c, *chap_i; chap_c = chap_get_challenge(chap); chap_i = chap_get_id(chap); response = login_new_response(request); response_keys = keys_new(); keys_add(response_keys, "CHAP_A", "5"); keys_add(response_keys, "CHAP_I", chap_i); keys_add(response_keys, "CHAP_C", chap_c); free(chap_i); free(chap_c); keys_save_pdu(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); } static struct pdu * login_receive_chap_r(struct connection *conn, struct auth_group *ag, struct chap *chap, const struct auth **authp, std::string &user) { struct pdu *request; struct keys *request_keys; const char *chap_n, *chap_r; const struct auth *auth; int error; request = login_receive(conn, false); request_keys = keys_new(); keys_load_pdu(request_keys, request); chap_n = keys_find(request_keys, "CHAP_N"); if (chap_n == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_N"); } chap_r = keys_find(request_keys, "CHAP_R"); if (chap_r == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_R"); } error = chap_receive(chap, chap_r); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); } /* * Verify the response. */ assert(ag->type() == auth_type::CHAP || ag->type() == auth_type::CHAP_MUTUAL); auth = ag->find_auth(chap_n); if (auth == NULL) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login with invalid user \"%s\"", chap_n); } error = chap_authenticate(chap, auth->secret()); if (error != 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "CHAP authentication failed for user \"%s\"", chap_n); } user = chap_n; keys_delete(request_keys); *authp = auth; return (request); } static void login_send_chap_success(struct pdu *request, const struct auth *auth, const std::string &user) { struct pdu *response; struct keys *request_keys, *response_keys; struct rchap *rchap; const char *chap_i, *chap_c; char *chap_r; int error; response = login_new_response(request); login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); /* * Actually, one more thing: mutual authentication. */ request_keys = keys_new(); keys_load_pdu(request_keys, request); chap_i = keys_find(request_keys, "CHAP_I"); chap_c = keys_find(request_keys, "CHAP_C"); if (chap_i != NULL || chap_c != NULL) { if (chap_i == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "initiator requested target " "authentication, but didn't send CHAP_I"); } if (chap_c == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "initiator requested target " "authentication, but didn't send CHAP_C"); } if (!auth->mutual()) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator requests target authentication " "for user \"%s\", but mutual user/secret " "is not set", user.c_str()); } log_debugx("performing mutual authentication as user \"%s\"", auth->mutual_user()); rchap = rchap_new(auth->mutual_secret()); error = rchap_receive(rchap, chap_i, chap_c); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed " "CHAP_I or CHAP_C"); } chap_r = rchap_get_response(rchap); rchap_delete(rchap); response_keys = keys_new(); keys_add(response_keys, "CHAP_N", auth->mutual_user()); keys_add(response_keys, "CHAP_R", chap_r); free(chap_r); keys_save_pdu(response_keys, response); keys_delete(response_keys); } else { log_debugx("initiator did not request target authentication"); } keys_delete(request_keys); pdu_send(response); pdu_delete(response); } void iscsi_connection::login_chap(struct auth_group *ag) { std::string user; const struct auth *auth; struct chap *chap; struct pdu *request; /* * Receive CHAP_A PDU. */ log_debugx("beginning CHAP authentication; waiting for CHAP_A"); request = login_receive_chap_a(&conn); /* * Generate the challenge. */ chap = chap_new(); /* * Send the challenge. */ log_debugx("sending CHAP_C, binary challenge size is %zd bytes", sizeof(chap->chap_challenge)); login_send_chap_c(request, chap); pdu_delete(request); /* * Receive CHAP_N/CHAP_R PDU and authenticate. */ log_debugx("waiting for CHAP_N/CHAP_R"); request = login_receive_chap_r(&conn, ag, chap, &auth, user); /* * Yay, authentication succeeded! */ log_debugx("authentication succeeded for user \"%s\"; " "transitioning to operational parameter negotiation", user.c_str()); login_send_chap_success(request, auth, user); pdu_delete(request); /* * Leave username and CHAP information for discovery(). */ conn_user = user; conn_chap = chap; } void iscsi_connection::login_negotiate_key(struct pdu *request, const char *name, const char *value, bool skipped_security, struct keys *response_keys) { int which; size_t tmp; assert(request->pdu_connection == &conn); if (strcmp(name, "InitiatorName") == 0) { if (!skipped_security) log_errx(1, "initiator resent InitiatorName"); } else if (strcmp(name, "SessionType") == 0) { if (!skipped_security) log_errx(1, "initiator resent SessionType"); } else if (strcmp(name, "TargetName") == 0) { if (!skipped_security) log_errx(1, "initiator resent TargetName"); } else if (strcmp(name, "InitiatorAlias") == 0) { conn_initiator_alias = value; } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ } else if (strcmp(name, "HeaderDigest") == 0) { /* * We don't handle digests for discovery sessions. */ if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; } which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("initiator prefers CRC32C " "for header digest; we'll use it"); conn.conn_header_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: log_debugx("initiator prefers not to do " "header digest; we'll comply"); keys_add(response_keys, name, "None"); break; default: log_warnx("initiator sent unrecognized " "HeaderDigest value \"%s\"; will use None", value); keys_add(response_keys, name, "None"); break; } } else if (strcmp(name, "DataDigest") == 0) { if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; } which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("initiator prefers CRC32C " "for data digest; we'll use it"); conn.conn_data_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: log_debugx("initiator prefers not to do " "data digest; we'll comply"); keys_add(response_keys, name, "None"); break; default: log_warnx("initiator sent unrecognized " "DataDigest value \"%s\"; will use None", value); keys_add(response_keys, name, "None"); break; } } else if (strcmp(name, "MaxConnections") == 0) { keys_add(response_keys, name, "1"); } else if (strcmp(name, "InitialR2T") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "ImmediateData") == 0) { if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; ImmediateData irrelevant"); keys_add(response_keys, name, "Irrelevant"); } else { if (strcmp(value, "Yes") == 0) { conn.conn_immediate_data = true; keys_add(response_keys, name, "Yes"); } else { conn.conn_immediate_data = false; keys_add(response_keys, name, "No"); } } } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid " "MaxRecvDataSegmentLength"); } /* * MaxRecvDataSegmentLength is a direction-specific parameter. * We'll limit our _send_ to what the initiator can handle but * our MaxRecvDataSegmentLength is not influenced by the * initiator in any way. */ if ((int)tmp > conn_max_send_data_segment_limit) { log_debugx("capping MaxRecvDataSegmentLength " "from %zd to %d", tmp, conn_max_send_data_segment_limit); tmp = conn_max_send_data_segment_limit; } conn.conn_max_send_data_segment_length = tmp; } else if (strcmp(name, "MaxBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid MaxBurstLength"); } if ((int)tmp > conn_max_burst_limit) { log_debugx("capping MaxBurstLength from %zd to %d", tmp, conn_max_burst_limit); tmp = conn_max_burst_limit; } conn.conn_max_burst_length = tmp; keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "FirstBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid FirstBurstLength"); } if ((int)tmp > conn_first_burst_limit) { log_debugx("capping FirstBurstLength from %zd to %d", tmp, conn_first_burst_limit); tmp = conn_first_burst_limit; } conn.conn_first_burst_length = tmp; keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "DefaultTime2Wait") == 0) { keys_add(response_keys, name, value); } else if (strcmp(name, "DefaultTime2Retain") == 0) { keys_add(response_keys, name, "0"); } else if (strcmp(name, "MaxOutstandingR2T") == 0) { keys_add(response_keys, name, "1"); } else if (strcmp(name, "DataPDUInOrder") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "DataSequenceInOrder") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { keys_add(response_keys, name, "0"); } else if (strcmp(name, "OFMarker") == 0) { keys_add(response_keys, name, "No"); } else if (strcmp(name, "IFMarker") == 0) { keys_add(response_keys, name, "No"); } else if (strcmp(name, "iSCSIProtocolLevel") == 0) { tmp = strtoul(value, NULL, 10); if (tmp > 2) tmp = 2; keys_add_int(response_keys, name, tmp); } else { log_debugx("unknown key \"%s\"; responding " "with NotUnderstood", name); keys_add(response_keys, name, "NotUnderstood"); } } static void login_redirect(struct pdu *request, const char *target_address) { struct pdu *response; struct iscsi_bhs_login_response *bhslr2; struct keys *response_keys; response = login_new_response(request); login_set_csg(response, login_csg(request)); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_status_class = 0x01; bhslr2->bhslr_status_detail = 0x01; response_keys = keys_new(); keys_add(response_keys, "TargetAddress", target_address); keys_save_pdu(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); } bool iscsi_connection::login_portal_redirect(struct pdu *request) { const struct portal_group *pg; pg = conn_portal->portal_group(); if (!pg->is_redirecting()) return (false); log_debugx("portal-group \"%s\" configured to redirect to %s", pg->name(), pg->redirection()); login_redirect(request, pg->redirection()); return (true); } bool iscsi_connection::login_target_redirect(struct pdu *request) { const char *target_address; assert(!conn_portal->portal_group()->is_redirecting()); if (conn_target == NULL) return (false); if (!conn_target->has_redirection()) return (false); target_address = conn_target->redirection(); log_debugx("target \"%s\" configured to redirect to %s", conn_target->name(), target_address); login_redirect(request, target_address); return (true); } void iscsi_connection::login_negotiate(struct pdu *request) { struct portal_group *pg = conn_portal->portal_group(); struct pdu *response; struct iscsi_bhs_login_response *bhslr2; struct keys *request_keys, *response_keys; int i; bool redirected, skipped_security; if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { /* * Query the kernel for various size limits. In case of * offload, it depends on hardware capabilities. */ assert(conn_target != NULL); conn_max_recv_data_segment_limit = (1 << 24) - 1; conn_max_send_data_segment_limit = (1 << 24) - 1; conn_max_burst_limit = (1 << 24) - 1; conn_first_burst_limit = (1 << 24) - 1; kernel_limits(pg->offload(), - conn.conn_socket, + conn_fd, &conn_max_recv_data_segment_limit, &conn_max_send_data_segment_limit, &conn_max_burst_limit, &conn_first_burst_limit); /* We expect legal, usable values at this point. */ assert(conn_max_recv_data_segment_limit >= 512); assert(conn_max_recv_data_segment_limit < (1 << 24)); assert(conn_max_send_data_segment_limit >= 512); assert(conn_max_send_data_segment_limit < (1 << 24)); assert(conn_max_burst_limit >= 512); assert(conn_max_burst_limit < (1 << 24)); assert(conn_first_burst_limit >= 512); assert(conn_first_burst_limit < (1 << 24)); assert(conn_first_burst_limit <= conn_max_burst_limit); /* * Limit default send length in case it won't be negotiated. * We can't do it for other limits, since they may affect both * sender and receiver operation, and we must obey defaults. */ if (conn_max_send_data_segment_limit < conn.conn_max_send_data_segment_length) { conn.conn_max_send_data_segment_length = conn_max_send_data_segment_limit; } } else { conn_max_recv_data_segment_limit = MAX_DATA_SEGMENT_LENGTH; conn_max_send_data_segment_limit = MAX_DATA_SEGMENT_LENGTH; } if (request == NULL) { log_debugx("beginning operational parameter negotiation; " "waiting for Login PDU"); request = login_receive(&conn, false); skipped_security = false; } else skipped_security = true; /* * RFC 3720, 10.13.5. Status-Class and Status-Detail, says * the redirection SHOULD be accepted by the initiator before * authentication, but MUST be accepted afterwards; that's * why we're doing it here and not earlier. */ redirected = login_target_redirect(request); if (redirected) { log_debugx("initiator redirected; exiting"); exit(0); } request_keys = keys_new(); keys_load_pdu(request_keys, request); response = login_new_response(request); bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; bhslr2->bhslr_tsih = htons(0xbadd); login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE); response_keys = keys_new(); if (skipped_security && conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn_target->has_alias()) keys_add(response_keys, "TargetAlias", conn_target->alias()); keys_add_int(response_keys, "TargetPortalGroupTag", pg->tag()); } for (i = 0; i < KEYS_MAX; i++) { if (request_keys->keys_names[i] == NULL) break; login_negotiate_key(request, request_keys->keys_names[i], request_keys->keys_values[i], skipped_security, response_keys); } /* * We'd started with usable values at our end. But a bad initiator * could have presented a large FirstBurstLength and then a smaller * MaxBurstLength (in that order) and because we process the key/value * pairs in the order they are in the request we might have ended up * with illegal values here. */ if (conn_session_type == CONN_SESSION_TYPE_NORMAL && conn.conn_first_burst_length > conn.conn_max_burst_length) { log_errx(1, "initiator sent FirstBurstLength > MaxBurstLength"); } conn.conn_max_recv_data_segment_length = conn_max_recv_data_segment_limit; keys_add_int(response_keys, "MaxRecvDataSegmentLength", conn.conn_max_recv_data_segment_length); log_debugx("operational parameter negotiation done; " "transitioning to Full Feature Phase"); keys_save_pdu(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); pdu_delete(request); keys_delete(request_keys); } void iscsi_connection::login_wait_transition() { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; log_debugx("waiting for state transition request"); request = login_receive(&conn, false); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "got no \"T\" flag after answering AuthMethod"); } log_debugx("got state transition request"); response = login_new_response(request); pdu_delete(request); login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); pdu_send(response); pdu_delete(response); login_negotiate(nullptr); } void iscsi_connection::login() { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; struct keys *request_keys, *response_keys; struct auth_group *ag; struct portal_group *pg; const char *initiator_name, *initiator_alias, *session_type, *target_name, *auth_method; bool redirected, fail, trans; /* * Handle the initial Login Request - figure out required authentication * method and either transition to the next phase, if no authentication * is required, or call appropriate authentication code. */ log_debugx("beginning Login Phase; waiting for Login PDU"); request = login_receive(&conn, true); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; if (bhslr->bhslr_tsih != 0) { login_send_error(request, 0x02, 0x0a); log_errx(1, "received Login PDU with non-zero TSIH"); } pg = conn_portal->portal_group(); memcpy(conn_initiator_isid, bhslr->bhslr_isid, sizeof(conn_initiator_isid)); /* * XXX: Implement the C flag some day. */ request_keys = keys_new(); keys_load_pdu(request_keys, request); assert(conn_initiator_name.empty()); initiator_name = keys_find(request_keys, "InitiatorName"); if (initiator_name == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without InitiatorName"); } if (valid_iscsi_name(initiator_name, log_warnx) == false) { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid InitiatorName"); } conn_initiator_name = initiator_name; log_set_peer_name(conn_initiator_name.c_str()); setproctitle("%s (%s)", conn_initiator_addr.c_str(), conn_initiator_name.c_str()); redirected = login_portal_redirect(request); if (redirected) { log_debugx("initiator redirected; exiting"); exit(0); } initiator_alias = keys_find(request_keys, "InitiatorAlias"); if (initiator_alias != NULL) conn_initiator_alias = initiator_alias; assert(conn_session_type == CONN_SESSION_TYPE_NONE); session_type = keys_find(request_keys, "SessionType"); if (session_type != NULL) { if (strcmp(session_type, "Normal") == 0) { conn_session_type = CONN_SESSION_TYPE_NORMAL; } else if (strcmp(session_type, "Discovery") == 0) { conn_session_type = CONN_SESSION_TYPE_DISCOVERY; } else { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid " "SessionType \"%s\"", session_type); } } else conn_session_type = CONN_SESSION_TYPE_NORMAL; assert(conn_target == NULL); if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { target_name = keys_find(request_keys, "TargetName"); if (target_name == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without TargetName"); } conn_port = pg->find_port(target_name); if (conn_port == NULL) { login_send_error(request, 0x02, 0x03); log_errx(1, "requested target \"%s\" not found", target_name); } conn_target = conn_port->target(); } /* * At this point we know what kind of authentication we need. */ if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { ag = conn_port->auth_group(); if (ag == nullptr) ag = conn_target->auth_group(); if (conn_port->auth_group() == nullptr && conn_target->private_auth()) { log_debugx("initiator requests to connect " "to target \"%s\"", conn_target->name()); } else { log_debugx("initiator requests to connect " "to target \"%s\"; %s", conn_target->name(), ag->label()); } } else { assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY); ag = pg->discovery_auth_group(); log_debugx("initiator requests discovery session; %s", ag->label()); } if (ag->type() == auth_type::DENY) { login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type is \"deny\""); } if (ag->type() == auth_type::UNKNOWN) { /* * This can happen with empty auth-group. */ login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type not set, denying access"); } /* * Enforce initiator-name and initiator-portal. */ if (!ag->initiator_permitted(initiator_name)) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed initiator names"); } if (!ag->initiator_permitted(conn_initiator_sa)) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed " "initiator portals"); } /* * Let's see if the initiator intends to do any kind of authentication * at all. */ if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { if (ag->type() != auth_type::NO_AUTHENTICATION) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator skipped the authentication, " "but authentication is required"); } keys_delete(request_keys); log_debugx("initiator skipped the authentication, " "and we don't need it; proceeding with negotiation"); login_negotiate(request); return; } fail = false; response = login_new_response(request); response_keys = keys_new(); trans = (bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0; auth_method = keys_find(request_keys, "AuthMethod"); if (ag->type() == auth_type::NO_AUTHENTICATION) { log_debugx("authentication not required"); if (auth_method == NULL || login_list_contains(auth_method, "None")) { keys_add(response_keys, "AuthMethod", "None"); } else { log_warnx("initiator requests " "AuthMethod \"%s\" instead of \"None\"", auth_method); keys_add(response_keys, "AuthMethod", "Reject"); } if (trans) login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); } else { log_debugx("CHAP authentication required"); if (auth_method == NULL || login_list_contains(auth_method, "CHAP")) { keys_add(response_keys, "AuthMethod", "CHAP"); } else { log_warnx("initiator requests unsupported " "AuthMethod \"%s\" instead of \"CHAP\"", auth_method); keys_add(response_keys, "AuthMethod", "Reject"); fail = true; } } if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn_target->has_alias()) keys_add(response_keys, "TargetAlias", conn_target->alias()); keys_add_int(response_keys, "TargetPortalGroupTag", pg->tag()); } keys_save_pdu(response_keys, response); pdu_send(response); pdu_delete(response); keys_delete(response_keys); pdu_delete(request); keys_delete(request_keys); if (fail) { log_debugx("sent reject for AuthMethod; exiting"); exit(1); } if (ag->type() != auth_type::NO_AUTHENTICATION) { login_chap(ag); login_negotiate(nullptr); } else if (trans) { login_negotiate(nullptr); } else { login_wait_transition(); } }