diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile index 787a608c37be..f96f3152e061 100644 --- a/usr.sbin/ctld/Makefile +++ b/usr.sbin/ctld/Makefile @@ -1,30 +1,30 @@ .include CFLAGS+=-I${SRCTOP}/contrib/libucl/include .PATH: ${SRCTOP}/contrib/libucl/include PACKAGE= iscsi -PROG= ctld -SRCS= ctld.c conf.c discovery.c isns.c kernel.c -SRCS+= login.c parse.y token.l y.tab.h uclparse.c +PROG_CXX= ctld +SRCS= ctld.cc conf.cc discovery.cc isns.cc kernel.cc +SRCS+= login.cc parse.y token.l y.tab.h uclparse.cc CFLAGS+= -I${.CURDIR} CFLAGS+= -I${SRCTOP}/sys CFLAGS+= -I${SRCTOP}/sys/cam/ctl CFLAGS+= -I${SRCTOP}/sys/dev/iscsi CFLAGS+= -I${SRCTOP}/lib/libiscsiutil #CFLAGS+= -DICL_KERNEL_PROXY NO_WCAST_ALIGN= MAN= ctld.8 ctl.conf.5 LIBADD= bsdxml iscsiutil md sbuf util ucl m nv YFLAGS+= -v CLEANFILES= y.tab.c y.tab.h y.output NO_WMISSING_VARIABLE_DECLARATIONS= .if ${MK_ISCSI} != "no" CFLAGS+= -DWANT_ISCSI .endif .include diff --git a/usr.sbin/ctld/conf.c b/usr.sbin/ctld/conf.cc similarity index 100% rename from usr.sbin/ctld/conf.c rename to usr.sbin/ctld/conf.cc diff --git a/usr.sbin/ctld/conf.h b/usr.sbin/ctld/conf.h index d8f036e67136..6e287ce70436 100644 --- a/usr.sbin/ctld/conf.h +++ b/usr.sbin/ctld/conf.h @@ -1,100 +1,104 @@ /*- * 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 __CONF_H__ #define __CONF_H__ /* * This file defines the interface between parse.y and the rest of * ctld. */ +__BEGIN_DECLS + bool auth_group_start(const char *name); void auth_group_finish(void); bool auth_group_add_chap(const char *user, const char *secret); bool auth_group_add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2); bool auth_group_add_initiator_name(const char *name); bool auth_group_add_initiator_portal(const char *portal); bool auth_group_set_type(const char *type); void conf_set_debug(int debug); void conf_set_isns_period(int period); void conf_set_isns_timeout(int timeout); void conf_set_maxproc(int maxproc); bool conf_set_pidfile_path(const char *path); void conf_set_timeout(int timeout); bool isns_add_server(const char *addr); bool portal_group_start(const char *name); void portal_group_finish(void); bool portal_group_add_listen(const char *listen, bool iser); bool portal_group_add_option(const char *name, const char *value); bool portal_group_set_discovery_auth_group(const char *name); bool portal_group_set_dscp(u_int dscp); bool portal_group_set_filter(const char *filter); void portal_group_set_foreign(void); bool portal_group_set_offload(const char *offload); bool portal_group_set_pcp(u_int pcp); bool portal_group_set_redirection(const char *addr); void portal_group_set_tag(uint16_t tag); bool target_start(const char *name); void target_finish(void); bool target_add_chap(const char *user, const char *secret); bool target_add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2); bool target_add_initiator_name(const char *name); bool target_add_initiator_portal(const char *addr); bool target_add_lun(u_int id, const char *name); bool target_add_portal_group(const char *pg_name, const char *ag_name); bool target_set_alias(const char *alias); bool target_set_auth_group(const char *name); bool target_set_auth_type(const char *type); bool target_set_physical_port(const char *pport); bool target_set_redirection(const char *addr); bool target_start_lun(u_int id); bool lun_start(const char *name); void lun_finish(void); bool lun_add_option(const char *name, const char *value); bool lun_set_backend(const char *value); bool lun_set_blocksize(size_t value); bool lun_set_ctl_lun(uint32_t value); bool lun_set_device_id(const char *value); bool lun_set_device_type(const char *value); bool lun_set_path(const char *value); bool lun_set_serial(const char *value); bool lun_set_size(uint64_t value); bool parse_conf(const char *path); +__END_DECLS + #endif /* !__CONF_H__ */ diff --git a/usr.sbin/ctld/ctld.c b/usr.sbin/ctld/ctld.cc similarity index 98% rename from usr.sbin/ctld/ctld.c rename to usr.sbin/ctld/ctld.cc index 21d833df5f83..f07ee7b34cd7 100644 --- a/usr.sbin/ctld/ctld.c +++ b/usr.sbin/ctld/ctld.cc @@ -1,2659 +1,2662 @@ /*- * 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 "conf.h" #include "ctld.h" #include "isns.h" static bool timed_out(void); #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); 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 uint16_t last_portal_group_tag = 0xff; 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, }; 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); } struct conf * conf_new(void) { struct conf *conf; - conf = calloc(1, sizeof(*conf)); + conf = reinterpret_cast(calloc(1, sizeof(*conf))); if (conf == NULL) log_err(1, "calloc"); TAILQ_INIT(&conf->conf_luns); TAILQ_INIT(&conf->conf_targets); TAILQ_INIT(&conf->conf_auth_groups); TAILQ_INIT(&conf->conf_ports); TAILQ_INIT(&conf->conf_portal_groups); TAILQ_INIT(&conf->conf_isns); conf->conf_isns_period = 900; conf->conf_isns_timeout = 5; conf->conf_debug = 0; conf->conf_timeout = 60; conf->conf_maxproc = 30; return (conf); } void conf_delete(struct conf *conf) { struct lun *lun, *ltmp; struct target *targ, *tmp; struct auth_group *ag, *cagtmp; struct portal_group *pg, *cpgtmp; struct isns *is, *istmp; assert(conf->conf_pidfh == NULL); TAILQ_FOREACH_SAFE(lun, &conf->conf_luns, l_next, ltmp) lun_delete(lun); TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp) target_delete(targ); TAILQ_FOREACH_SAFE(ag, &conf->conf_auth_groups, ag_next, cagtmp) auth_group_delete(ag); TAILQ_FOREACH_SAFE(pg, &conf->conf_portal_groups, pg_next, cpgtmp) portal_group_delete(pg); TAILQ_FOREACH_SAFE(is, &conf->conf_isns, i_next, istmp) isns_delete(is); assert(TAILQ_EMPTY(&conf->conf_ports)); free(conf->conf_pidfile_path); free(conf); } static struct auth * auth_new(struct auth_group *ag) { struct auth *auth; - auth = calloc(1, sizeof(*auth)); + auth = reinterpret_cast(calloc(1, sizeof(*auth))); if (auth == NULL) log_err(1, "calloc"); auth->a_auth_group = ag; TAILQ_INSERT_TAIL(&ag->ag_auths, auth, a_next); return (auth); } static void auth_delete(struct auth *auth) { TAILQ_REMOVE(&auth->a_auth_group->ag_auths, auth, a_next); free(auth->a_user); free(auth->a_secret); free(auth->a_mutual_user); free(auth->a_mutual_secret); free(auth); } const struct auth * auth_find(const struct auth_group *ag, const char *user) { const struct auth *auth; TAILQ_FOREACH(auth, &ag->ag_auths, a_next) { if (strcmp(auth->a_user, user) == 0) return (auth); } return (NULL); } static void auth_check_secret_length(struct auth *auth) { size_t len; len = strlen(auth->a_secret); if (len > 16) { if (auth->a_auth_group->ag_name != NULL) log_warnx("secret for user \"%s\", auth-group \"%s\", " "is too long; it should be at most 16 characters " "long", auth->a_user, auth->a_auth_group->ag_name); else log_warnx("secret for user \"%s\", target \"%s\", " "is too long; it should be at most 16 characters " "long", auth->a_user, auth->a_auth_group->ag_target->t_name); } if (len < 12) { if (auth->a_auth_group->ag_name != NULL) log_warnx("secret for user \"%s\", auth-group \"%s\", " "is too short; it should be at least 12 characters " "long", auth->a_user, auth->a_auth_group->ag_name); else log_warnx("secret for user \"%s\", target \"%s\", " "is too short; it should be at least 12 characters " "long", auth->a_user, auth->a_auth_group->ag_target->t_name); } if (auth->a_mutual_secret != NULL) { len = strlen(auth->a_mutual_secret); if (len > 16) { if (auth->a_auth_group->ag_name != NULL) log_warnx("mutual secret for user \"%s\", " "auth-group \"%s\", is too long; it should " "be at most 16 characters long", auth->a_user, auth->a_auth_group->ag_name); else log_warnx("mutual secret for user \"%s\", " "target \"%s\", is too long; it should " "be at most 16 characters long", auth->a_user, auth->a_auth_group->ag_target->t_name); } if (len < 12) { if (auth->a_auth_group->ag_name != NULL) log_warnx("mutual secret for user \"%s\", " "auth-group \"%s\", is too short; it " "should be at least 12 characters long", auth->a_user, auth->a_auth_group->ag_name); else log_warnx("mutual secret for user \"%s\", " "target \"%s\", is too short; it should be " "at least 12 characters long", auth->a_user, auth->a_auth_group->ag_target->t_name); } } } bool auth_new_chap(struct auth_group *ag, const char *user, const char *secret) { struct auth *auth; if (ag->ag_type == AG_TYPE_UNKNOWN) ag->ag_type = AG_TYPE_CHAP; if (ag->ag_type != AG_TYPE_CHAP) { if (ag->ag_name != NULL) log_warnx("cannot mix \"chap\" authentication with " "other types for auth-group \"%s\"", ag->ag_name); else log_warnx("cannot mix \"chap\" authentication with " "other types for target \"%s\"", ag->ag_target->t_name); return (false); } auth = auth_new(ag); auth->a_user = checked_strdup(user); auth->a_secret = checked_strdup(secret); auth_check_secret_length(auth); return (true); } bool auth_new_chap_mutual(struct auth_group *ag, const char *user, const char *secret, const char *user2, const char *secret2) { struct auth *auth; if (ag->ag_type == AG_TYPE_UNKNOWN) ag->ag_type = AG_TYPE_CHAP_MUTUAL; if (ag->ag_type != AG_TYPE_CHAP_MUTUAL) { if (ag->ag_name != NULL) log_warnx("cannot mix \"chap-mutual\" authentication " "with other types for auth-group \"%s\"", ag->ag_name); else log_warnx("cannot mix \"chap-mutual\" authentication " "with other types for target \"%s\"", ag->ag_target->t_name); return (false); } auth = auth_new(ag); auth->a_user = checked_strdup(user); auth->a_secret = checked_strdup(secret); auth->a_mutual_user = checked_strdup(user2); auth->a_mutual_secret = checked_strdup(secret2); auth_check_secret_length(auth); return (true); } bool auth_name_new(struct auth_group *ag, const char *name) { struct auth_name *an; - an = calloc(1, sizeof(*an)); + an = reinterpret_cast(calloc(1, sizeof(*an))); if (an == NULL) log_err(1, "calloc"); an->an_auth_group = ag; an->an_initiator_name = checked_strdup(name); TAILQ_INSERT_TAIL(&ag->ag_names, an, an_next); return (true); } static void auth_name_delete(struct auth_name *an) { TAILQ_REMOVE(&an->an_auth_group->ag_names, an, an_next); free(an->an_initiator_name); free(an); } bool auth_name_defined(const struct auth_group *ag) { if (TAILQ_EMPTY(&ag->ag_names)) return (false); return (true); } const struct auth_name * auth_name_find(const struct auth_group *ag, const char *name) { const struct auth_name *auth_name; TAILQ_FOREACH(auth_name, &ag->ag_names, an_next) { if (strcmp(auth_name->an_initiator_name, name) == 0) return (auth_name); } return (NULL); } bool auth_name_check(const struct auth_group *ag, const char *initiator_name) { if (!auth_name_defined(ag)) return (true); if (auth_name_find(ag, initiator_name) == NULL) return (false); return (true); } bool auth_portal_new(struct auth_group *ag, const char *portal) { struct auth_portal *ap; char *net, *mask, *str, *tmp; int len, dm, m; - ap = calloc(1, sizeof(*ap)); + ap = reinterpret_cast(calloc(1, sizeof(*ap))); if (ap == NULL) log_err(1, "calloc"); ap->ap_auth_group = ag; ap->ap_initiator_portal = checked_strdup(portal); mask = str = checked_strdup(portal); net = strsep(&mask, "/"); if (net[0] == '[') net++; len = strlen(net); if (len == 0) goto error; if (net[len - 1] == ']') net[len - 1] = 0; if (strchr(net, ':') != NULL) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ap->ap_sa; sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = AF_INET6; if (inet_pton(AF_INET6, net, &sin6->sin6_addr) <= 0) goto error; dm = 128; } else { struct sockaddr_in *sin = (struct sockaddr_in *)&ap->ap_sa; sin->sin_len = sizeof(*sin); sin->sin_family = AF_INET; if (inet_pton(AF_INET, net, &sin->sin_addr) <= 0) goto error; dm = 32; } if (mask != NULL) { m = strtol(mask, &tmp, 0); if (m < 0 || m > dm || tmp[0] != 0) goto error; } else m = dm; ap->ap_mask = m; free(str); TAILQ_INSERT_TAIL(&ag->ag_portals, ap, ap_next); return (true); error: free(str); free(ap); log_warnx("incorrect initiator portal \"%s\"", portal); return (false); } static void auth_portal_delete(struct auth_portal *ap) { TAILQ_REMOVE(&ap->ap_auth_group->ag_portals, ap, ap_next); free(ap->ap_initiator_portal); free(ap); } bool auth_portal_defined(const struct auth_group *ag) { if (TAILQ_EMPTY(&ag->ag_portals)) return (false); return (true); } const struct auth_portal * auth_portal_find(const struct auth_group *ag, const struct sockaddr_storage *ss) { const struct auth_portal *ap; const uint8_t *a, *b; int i; uint8_t bmask; TAILQ_FOREACH(ap, &ag->ag_portals, ap_next) { if (ap->ap_sa.ss_family != ss->ss_family) continue; if (ss->ss_family == AF_INET) { a = (const uint8_t *) &((const struct sockaddr_in *)ss)->sin_addr; b = (const uint8_t *) &((const struct sockaddr_in *)&ap->ap_sa)->sin_addr; } else { a = (const uint8_t *) &((const struct sockaddr_in6 *)ss)->sin6_addr; b = (const uint8_t *) &((const struct sockaddr_in6 *)&ap->ap_sa)->sin6_addr; } for (i = 0; i < ap->ap_mask / 8; i++) { if (a[i] != b[i]) goto next; } if (ap->ap_mask % 8) { bmask = 0xff << (8 - (ap->ap_mask % 8)); if ((a[i] & bmask) != (b[i] & bmask)) goto next; } return (ap); next: ; } return (NULL); } bool auth_portal_check(const struct auth_group *ag, const struct sockaddr_storage *sa) { if (!auth_portal_defined(ag)) return (true); if (auth_portal_find(ag, sa) == NULL) return (false); return (true); } struct auth_group * auth_group_new(struct conf *conf, const char *name) { struct auth_group *ag; if (name != NULL) { ag = auth_group_find(conf, name); if (ag != NULL) { log_warnx("duplicated auth-group \"%s\"", name); return (NULL); } } - ag = calloc(1, sizeof(*ag)); + ag = reinterpret_cast(calloc(1, sizeof(*ag))); if (ag == NULL) log_err(1, "calloc"); if (name != NULL) ag->ag_name = checked_strdup(name); TAILQ_INIT(&ag->ag_auths); TAILQ_INIT(&ag->ag_names); TAILQ_INIT(&ag->ag_portals); ag->ag_conf = conf; TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next); return (ag); } void auth_group_delete(struct auth_group *ag) { struct auth *auth, *auth_tmp; struct auth_name *auth_name, *auth_name_tmp; struct auth_portal *auth_portal, *auth_portal_tmp; TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next); TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, auth_tmp) auth_delete(auth); TAILQ_FOREACH_SAFE(auth_name, &ag->ag_names, an_next, auth_name_tmp) auth_name_delete(auth_name); TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_portals, ap_next, auth_portal_tmp) auth_portal_delete(auth_portal); free(ag->ag_name); free(ag); } struct auth_group * auth_group_find(const struct conf *conf, const char *name) { struct auth_group *ag; assert(name != NULL); TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { if (ag->ag_name != NULL && strcmp(ag->ag_name, name) == 0) return (ag); } return (NULL); } static struct portal * portal_new(struct portal_group *pg) { struct portal *portal; - portal = calloc(1, sizeof(*portal)); + portal = reinterpret_cast(calloc(1, sizeof(*portal))); if (portal == NULL) log_err(1, "calloc"); TAILQ_INIT(&portal->p_targets); portal->p_portal_group = pg; TAILQ_INSERT_TAIL(&pg->pg_portals, portal, p_next); return (portal); } static void portal_delete(struct portal *portal) { TAILQ_REMOVE(&portal->p_portal_group->pg_portals, portal, p_next); if (portal->p_ai != NULL) freeaddrinfo(portal->p_ai); free(portal->p_listen); free(portal); } struct portal_group * portal_group_new(struct conf *conf, const char *name) { struct portal_group *pg; pg = portal_group_find(conf, name); if (pg != NULL) { log_warnx("duplicated portal-group \"%s\"", name); return (NULL); } - pg = calloc(1, sizeof(*pg)); + pg = reinterpret_cast(calloc(1, sizeof(*pg))); if (pg == NULL) log_err(1, "calloc"); pg->pg_name = checked_strdup(name); pg->pg_options = nvlist_create(0); TAILQ_INIT(&pg->pg_portals); TAILQ_INIT(&pg->pg_ports); pg->pg_conf = conf; pg->pg_tag = 0; /* Assigned later in conf_apply(). */ pg->pg_dscp = -1; pg->pg_pcp = -1; TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next); return (pg); } void portal_group_delete(struct portal_group *pg) { struct portal *portal, *tmp; struct port *port, *tport; TAILQ_FOREACH_SAFE(port, &pg->pg_ports, p_pgs, tport) port_delete(port); TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next); TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp) portal_delete(portal); nvlist_destroy(pg->pg_options); free(pg->pg_name); free(pg->pg_offload); free(pg->pg_redirection); free(pg); } struct portal_group * portal_group_find(const struct conf *conf, const char *name) { struct portal_group *pg; TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { if (strcmp(pg->pg_name, name) == 0) return (pg); } return (NULL); } static int parse_addr_port(char *arg, const char *def_port, struct addrinfo **ai) { struct addrinfo hints; char *str, *addr, *ch; const char *port; int error, colons = 0; str = arg = strdup(arg); if (arg[0] == '[') { /* * IPv6 address in square brackets, perhaps with port. */ arg++; addr = strsep(&arg, "]"); if (arg == NULL) { free(str); return (1); } if (arg[0] == '\0') { port = def_port; } else if (arg[0] == ':') { port = arg + 1; } else { free(str); return (1); } } else { /* * Either IPv6 address without brackets - and without * a port - or IPv4 address. Just count the colons. */ for (ch = arg; *ch != '\0'; ch++) { if (*ch == ':') colons++; } if (colons > 1) { addr = arg; port = def_port; } else { addr = strsep(&arg, ":"); if (arg == NULL) port = def_port; else port = arg; } } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; error = getaddrinfo(addr, port, &hints, ai); free(str); return ((error != 0) ? 1 : 0); } bool portal_group_add_portal(struct portal_group *pg, const char *value, bool iser) { struct portal *portal; portal = portal_new(pg); portal->p_listen = checked_strdup(value); portal->p_iser = iser; if (parse_addr_port(portal->p_listen, "3260", &portal->p_ai)) { log_warnx("invalid listen address %s", portal->p_listen); portal_delete(portal); return (false); } /* * XXX: getaddrinfo(3) may return multiple addresses; we should turn * those into multiple portals. */ return (true); } bool isns_new(struct conf *conf, const char *addr) { struct isns *isns; - isns = calloc(1, sizeof(*isns)); + isns = reinterpret_cast(calloc(1, sizeof(*isns))); if (isns == NULL) log_err(1, "calloc"); isns->i_conf = conf; TAILQ_INSERT_TAIL(&conf->conf_isns, isns, i_next); isns->i_addr = checked_strdup(addr); if (parse_addr_port(isns->i_addr, "3205", &isns->i_ai)) { log_warnx("invalid iSNS address %s", isns->i_addr); isns_delete(isns); return (false); } /* * XXX: getaddrinfo(3) may return multiple addresses; we should turn * those into multiple servers. */ return (true); } void isns_delete(struct isns *isns) { TAILQ_REMOVE(&isns->i_conf->conf_isns, isns, i_next); free(isns->i_addr); if (isns->i_ai != NULL) freeaddrinfo(isns->i_ai); free(isns); } static int isns_do_connect(struct isns *isns) { int s; s = socket(isns->i_ai->ai_family, isns->i_ai->ai_socktype, isns->i_ai->ai_protocol); if (s < 0) { log_warn("socket(2) failed for %s", isns->i_addr); return (-1); } if (connect(s, isns->i_ai->ai_addr, isns->i_ai->ai_addrlen)) { log_warn("connect(2) failed for %s", isns->i_addr); close(s); return (-1); } return(s); } static int isns_do_register(struct isns *isns, int s, const char *hostname) { struct conf *conf = isns->i_conf; struct target *target; struct portal *portal; struct portal_group *pg; struct port *port; struct isns_req *req; int res = 0; uint32_t error; req = isns_req_create(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT); isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); isns_req_add_delim(req); isns_req_add_str(req, 1, hostname); isns_req_add_32(req, 2, 2); /* 2 -- iSCSI */ isns_req_add_32(req, 6, conf->conf_isns_period); TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { if (pg->pg_unassigned) continue; TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { isns_req_add_addr(req, 16, portal->p_ai); isns_req_add_port(req, 17, portal->p_ai); } } TAILQ_FOREACH(target, &conf->conf_targets, t_next) { isns_req_add_str(req, 32, target->t_name); isns_req_add_32(req, 33, 1); /* 1 -- Target*/ if (target->t_alias != NULL) isns_req_add_str(req, 34, target->t_alias); TAILQ_FOREACH(port, &target->t_ports, p_ts) { if ((pg = port->p_portal_group) == NULL) continue; isns_req_add_32(req, 51, pg->pg_tag); TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { isns_req_add_addr(req, 49, portal->p_ai); isns_req_add_port(req, 50, portal->p_ai); } } } res = isns_req_send(s, req); if (res < 0) { log_warn("send(2) failed for %s", isns->i_addr); goto quit; } res = isns_req_receive(s, req); if (res < 0) { log_warn("receive(2) failed for %s", isns->i_addr); goto quit; } error = isns_req_get_status(req); if (error != 0) { log_warnx("iSNS register error %d for %s", error, isns->i_addr); res = -1; } quit: isns_req_free(req); return (res); } static int isns_do_check(struct isns *isns, int s, const char *hostname) { struct conf *conf = isns->i_conf; struct isns_req *req; int res = 0; uint32_t error; req = isns_req_create(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT); isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); isns_req_add_str(req, 1, hostname); isns_req_add_delim(req); isns_req_add(req, 2, 0, NULL); res = isns_req_send(s, req); if (res < 0) { log_warn("send(2) failed for %s", isns->i_addr); goto quit; } res = isns_req_receive(s, req); if (res < 0) { log_warn("receive(2) failed for %s", isns->i_addr); goto quit; } error = isns_req_get_status(req); if (error != 0) { log_warnx("iSNS check error %d for %s", error, isns->i_addr); res = -1; } quit: isns_req_free(req); return (res); } static int isns_do_deregister(struct isns *isns, int s, const char *hostname) { struct conf *conf = isns->i_conf; struct isns_req *req; int res = 0; uint32_t error; req = isns_req_create(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT); isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); isns_req_add_delim(req); isns_req_add_str(req, 1, hostname); res = isns_req_send(s, req); if (res < 0) { log_warn("send(2) failed for %s", isns->i_addr); goto quit; } res = isns_req_receive(s, req); if (res < 0) { log_warn("receive(2) failed for %s", isns->i_addr); goto quit; } error = isns_req_get_status(req); if (error != 0) { log_warnx("iSNS deregister error %d for %s", error, isns->i_addr); res = -1; } quit: isns_req_free(req); return (res); } void isns_register(struct isns *isns, struct isns *oldisns) { struct conf *conf = isns->i_conf; int error, s; char hostname[256]; if (TAILQ_EMPTY(&conf->conf_targets) || TAILQ_EMPTY(&conf->conf_portal_groups)) return; set_timeout(conf->conf_isns_timeout, false); s = isns_do_connect(isns); if (s < 0) { set_timeout(0, false); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); if (oldisns == NULL || TAILQ_EMPTY(&oldisns->i_conf->conf_targets)) oldisns = isns; isns_do_deregister(oldisns, s, hostname); isns_do_register(isns, s, hostname); close(s); set_timeout(0, false); } void isns_check(struct isns *isns) { struct conf *conf = isns->i_conf; int error, s, res; char hostname[256]; if (TAILQ_EMPTY(&conf->conf_targets) || TAILQ_EMPTY(&conf->conf_portal_groups)) return; set_timeout(conf->conf_isns_timeout, false); s = isns_do_connect(isns); if (s < 0) { set_timeout(0, false); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); res = isns_do_check(isns, s, hostname); if (res < 0) { isns_do_deregister(isns, s, hostname); isns_do_register(isns, s, hostname); } close(s); set_timeout(0, false); } void isns_deregister(struct isns *isns) { struct conf *conf = isns->i_conf; int error, s; char hostname[256]; if (TAILQ_EMPTY(&conf->conf_targets) || TAILQ_EMPTY(&conf->conf_portal_groups)) return; set_timeout(conf->conf_isns_timeout, false); s = isns_do_connect(isns); if (s < 0) return; error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); isns_do_deregister(isns, s, hostname); close(s); set_timeout(0, false); } struct pport * pport_new(struct kports *kports, const char *name, uint32_t ctl_port) { struct pport *pp; - pp = calloc(1, sizeof(*pp)); + pp = reinterpret_cast(calloc(1, sizeof(*pp))); if (pp == NULL) log_err(1, "calloc"); pp->pp_kports = kports; pp->pp_name = checked_strdup(name); pp->pp_ctl_port = ctl_port; TAILQ_INIT(&pp->pp_ports); TAILQ_INSERT_TAIL(&kports->pports, pp, pp_next); return (pp); } struct pport * pport_find(const struct kports *kports, const char *name) { struct pport *pp; TAILQ_FOREACH(pp, &kports->pports, pp_next) { if (strcasecmp(pp->pp_name, name) == 0) return (pp); } return (NULL); } struct pport * pport_copy(struct pport *pp, struct kports *kports) { struct pport *ppnew; ppnew = pport_new(kports, pp->pp_name, pp->pp_ctl_port); return (ppnew); } void pport_delete(struct pport *pp) { struct port *port, *tport; TAILQ_FOREACH_SAFE(port, &pp->pp_ports, p_ts, tport) port_delete(port); TAILQ_REMOVE(&pp->pp_kports->pports, pp, pp_next); free(pp->pp_name); free(pp); } struct port * port_new(struct conf *conf, struct target *target, struct portal_group *pg) { struct port *port; char *name; int ret; ret = asprintf(&name, "%s-%s", pg->pg_name, target->t_name); if (ret <= 0) log_err(1, "asprintf"); if (port_find(conf, name) != NULL) { log_warnx("duplicate port \"%s\"", name); free(name); return (NULL); } - port = calloc(1, sizeof(*port)); + port = reinterpret_cast(calloc(1, sizeof(*port))); if (port == NULL) log_err(1, "calloc"); port->p_conf = conf; port->p_name = name; TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); port->p_target = target; TAILQ_INSERT_TAIL(&pg->pg_ports, port, p_pgs); port->p_portal_group = pg; return (port); } struct port * port_new_ioctl(struct conf *conf, struct kports *kports, struct target *target, int pp, int vp) { struct pport *pport; struct port *port; char *pname; char *name; int ret; ret = asprintf(&pname, "ioctl/%d/%d", pp, vp); if (ret <= 0) { log_err(1, "asprintf"); return (NULL); } pport = pport_find(kports, pname); if (pport != NULL) { free(pname); return (port_new_pp(conf, target, pport)); } ret = asprintf(&name, "%s-%s", pname, target->t_name); free(pname); if (ret <= 0) log_err(1, "asprintf"); if (port_find(conf, name) != NULL) { log_warnx("duplicate port \"%s\"", name); free(name); return (NULL); } - port = calloc(1, sizeof(*port)); + port = reinterpret_cast(calloc(1, sizeof(*port))); if (port == NULL) log_err(1, "calloc"); port->p_conf = conf; port->p_name = name; port->p_ioctl_port = true; port->p_ioctl_pp = pp; port->p_ioctl_vp = vp; TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); port->p_target = target; return (port); } struct port * port_new_pp(struct conf *conf, struct target *target, struct pport *pp) { struct port *port; char *name; int ret; ret = asprintf(&name, "%s-%s", pp->pp_name, target->t_name); if (ret <= 0) log_err(1, "asprintf"); if (port_find(conf, name) != NULL) { log_warnx("duplicate port \"%s\"", name); free(name); return (NULL); } - port = calloc(1, sizeof(*port)); + port = reinterpret_cast(calloc(1, sizeof(*port))); if (port == NULL) log_err(1, "calloc"); port->p_conf = conf; port->p_name = name; TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); port->p_target = target; TAILQ_INSERT_TAIL(&pp->pp_ports, port, p_pps); port->p_pport = pp; return (port); } struct port * port_find(const struct conf *conf, const char *name) { struct port *port; TAILQ_FOREACH(port, &conf->conf_ports, p_next) { if (strcasecmp(port->p_name, name) == 0) return (port); } return (NULL); } struct port * port_find_in_pg(const struct portal_group *pg, const char *target) { struct port *port; TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) { if (strcasecmp(port->p_target->t_name, target) == 0) return (port); } return (NULL); } void port_delete(struct port *port) { if (port->p_portal_group) TAILQ_REMOVE(&port->p_portal_group->pg_ports, port, p_pgs); if (port->p_pport) TAILQ_REMOVE(&port->p_pport->pp_ports, port, p_pps); if (port->p_target) TAILQ_REMOVE(&port->p_target->t_ports, port, p_ts); TAILQ_REMOVE(&port->p_conf->conf_ports, port, p_next); free(port->p_name); free(port); } bool port_is_dummy(struct port *port) { if (port->p_portal_group) { if (port->p_portal_group->pg_foreign) return (true); if (TAILQ_EMPTY(&port->p_portal_group->pg_portals)) return (true); } return (false); } struct target * target_new(struct conf *conf, const char *name) { struct target *targ; int i, len; targ = target_find(conf, name); if (targ != NULL) { log_warnx("duplicated target \"%s\"", name); return (NULL); } if (valid_iscsi_name(name, log_warnx) == false) { return (NULL); } - targ = calloc(1, sizeof(*targ)); + targ = reinterpret_cast(calloc(1, sizeof(*targ))); if (targ == NULL) log_err(1, "calloc"); targ->t_name = checked_strdup(name); /* * RFC 3722 requires us to normalize the name to lowercase. */ len = strlen(name); for (i = 0; i < len; i++) targ->t_name[i] = tolower(targ->t_name[i]); targ->t_conf = conf; TAILQ_INIT(&targ->t_ports); TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); return (targ); } void target_delete(struct target *targ) { struct port *port, *tport; TAILQ_FOREACH_SAFE(port, &targ->t_ports, p_ts, tport) port_delete(port); TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); free(targ->t_pport); free(targ->t_name); free(targ->t_redirection); free(targ); } struct target * target_find(struct conf *conf, const char *name) { struct target *targ; TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { if (strcasecmp(targ->t_name, name) == 0) return (targ); } return (NULL); } struct lun * lun_new(struct conf *conf, const char *name) { struct lun *lun; lun = lun_find(conf, name); if (lun != NULL) { log_warnx("duplicated lun \"%s\"", name); return (NULL); } - lun = calloc(1, sizeof(*lun)); + lun = reinterpret_cast(calloc(1, sizeof(*lun))); if (lun == NULL) log_err(1, "calloc"); lun->l_conf = conf; lun->l_name = checked_strdup(name); lun->l_options = nvlist_create(0); TAILQ_INSERT_TAIL(&conf->conf_luns, lun, l_next); lun->l_ctl_lun = -1; return (lun); } void lun_delete(struct lun *lun) { struct target *targ; int i; TAILQ_FOREACH(targ, &lun->l_conf->conf_targets, t_next) { for (i = 0; i < MAX_LUNS; i++) { if (targ->t_luns[i] == lun) targ->t_luns[i] = NULL; } } TAILQ_REMOVE(&lun->l_conf->conf_luns, lun, l_next); nvlist_destroy(lun->l_options); free(lun->l_name); free(lun->l_backend); free(lun->l_device_id); free(lun->l_path); free(lun->l_scsiname); free(lun->l_serial); free(lun); } struct lun * lun_find(const struct conf *conf, const char *name) { struct lun *lun; TAILQ_FOREACH(lun, &conf->conf_luns, l_next) { if (strcmp(lun->l_name, name) == 0) return (lun); } return (NULL); } void lun_set_scsiname(struct lun *lun, const char *value) { free(lun->l_scsiname); lun->l_scsiname = checked_strdup(value); } 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); } #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) { } static struct ctld_connection * connection_new(struct portal *portal, int fd, const char *host, const struct sockaddr *client_sa) { struct ctld_connection *conn; - conn = calloc(1, sizeof(*conn)); + conn = reinterpret_cast(calloc(1, sizeof(*conn))); if (conn == NULL) log_err(1, "calloc"); connection_init(&conn->conn, &conn_ops, proxy_mode); conn->conn.conn_socket = fd; conn->conn_portal = portal; conn->conn_initiator_addr = checked_strdup(host); memcpy(&conn->conn_initiator_sa, client_sa, client_sa->sa_len); return (conn); } #if 0 static void options_print(const char *prefix, nvlist_t *nvl) { const char *name; void *cookie; cookie = NULL; while ((name = nvlist_next(nvl, NULL, &cookie)) != NULL) { fprintf(stderr, "%soption %s %s\n", prefix, name, nvlist_get_string(nvl, name)); } } static void conf_print(struct conf *conf) { struct auth_group *ag; struct auth *auth; struct auth_name *auth_name; struct auth_portal *auth_portal; struct portal_group *pg; struct portal *portal; struct target *targ; struct lun *lun; TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { fprintf(stderr, "auth-group %s {\n", ag->ag_name); TAILQ_FOREACH(auth, &ag->ag_auths, a_next) fprintf(stderr, "\t chap-mutual %s %s %s %s\n", auth->a_user, auth->a_secret, auth->a_mutual_user, auth->a_mutual_secret); TAILQ_FOREACH(auth_name, &ag->ag_names, an_next) fprintf(stderr, "\t initiator-name %s\n", auth_name->an_initiator_name); TAILQ_FOREACH(auth_portal, &ag->ag_portals, ap_next) fprintf(stderr, "\t initiator-portal %s\n", auth_portal->ap_initiator_portal); fprintf(stderr, "}\n"); } TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { fprintf(stderr, "portal-group %s {\n", pg->pg_name); TAILQ_FOREACH(portal, &pg->pg_portals, p_next) fprintf(stderr, "\t listen %s\n", portal->p_listen); options_print("\t", pg->pg_options); fprintf(stderr, "}\n"); } TAILQ_FOREACH(lun, &conf->conf_luns, l_next) { fprintf(stderr, "\tlun %s {\n", lun->l_name); fprintf(stderr, "\t\tpath %s\n", lun->l_path); options_print("\t\t", lun->l_options); fprintf(stderr, "\t}\n"); } TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { fprintf(stderr, "target %s {\n", targ->t_name); if (targ->t_alias != NULL) fprintf(stderr, "\t alias %s\n", targ->t_alias); fprintf(stderr, "}\n"); } } #endif static bool conf_verify_lun(struct lun *lun) { const struct lun *lun2; if (lun->l_backend == NULL) lun->l_backend = checked_strdup("block"); if (strcmp(lun->l_backend, "block") == 0) { if (lun->l_path == NULL) { log_warnx("missing path for lun \"%s\"", lun->l_name); return (false); } } else if (strcmp(lun->l_backend, "ramdisk") == 0) { if (lun->l_size == 0) { log_warnx("missing size for ramdisk-backed lun \"%s\"", lun->l_name); return (false); } if (lun->l_path != NULL) { log_warnx("path must not be specified " "for ramdisk-backed lun \"%s\"", lun->l_name); return (false); } } if (lun->l_blocksize == 0) { if (lun->l_device_type == T_CDROM) lun->l_blocksize = DEFAULT_CD_BLOCKSIZE; else lun->l_blocksize = DEFAULT_BLOCKSIZE; } else if (lun->l_blocksize < 0) { log_warnx("invalid blocksize for lun \"%s\"; " "must be larger than 0", lun->l_name); return (false); } if (lun->l_size != 0 && lun->l_size % lun->l_blocksize != 0) { log_warnx("invalid size for lun \"%s\"; " "must be multiple of blocksize", lun->l_name); return (false); } TAILQ_FOREACH(lun2, &lun->l_conf->conf_luns, l_next) { if (lun == lun2) continue; if (lun->l_path != NULL && lun2->l_path != NULL && strcmp(lun->l_path, lun2->l_path) == 0) { log_debugx("WARNING: path \"%s\" duplicated " "between lun \"%s\", and " "lun \"%s\"", lun->l_path, lun->l_name, lun2->l_name); } } return (true); } bool conf_verify(struct conf *conf) { struct auth_group *ag; struct portal_group *pg; struct port *port; struct target *targ; struct lun *lun; bool found; int i; if (conf->conf_pidfile_path == NULL) conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE); TAILQ_FOREACH(lun, &conf->conf_luns, l_next) { if (!conf_verify_lun(lun)) return (false); } TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { if (targ->t_auth_group == NULL) { targ->t_auth_group = auth_group_find(conf, "default"); assert(targ->t_auth_group != NULL); } if (TAILQ_EMPTY(&targ->t_ports)) { pg = portal_group_find(conf, "default"); assert(pg != NULL); port_new(conf, targ, pg); } found = false; for (i = 0; i < MAX_LUNS; i++) { if (targ->t_luns[i] != NULL) found = true; } if (!found && targ->t_redirection == NULL) { log_warnx("no LUNs defined for target \"%s\"", targ->t_name); } if (found && targ->t_redirection != NULL) { log_debugx("target \"%s\" contains luns, " " but configured for redirection", targ->t_name); } } TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { assert(pg->pg_name != NULL); if (pg->pg_discovery_auth_group == NULL) { pg->pg_discovery_auth_group = auth_group_find(conf, "default"); assert(pg->pg_discovery_auth_group != NULL); } if (pg->pg_discovery_filter == PG_FILTER_UNKNOWN) pg->pg_discovery_filter = PG_FILTER_NONE; if (pg->pg_redirection != NULL) { if (!TAILQ_EMPTY(&pg->pg_ports)) { log_debugx("portal-group \"%s\" assigned " "to target, but configured " "for redirection", pg->pg_name); } pg->pg_unassigned = false; } else if (!TAILQ_EMPTY(&pg->pg_ports)) { pg->pg_unassigned = false; } else { if (strcmp(pg->pg_name, "default") != 0) log_warnx("portal-group \"%s\" not assigned " "to any target", pg->pg_name); pg->pg_unassigned = true; } } TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { if (ag->ag_name == NULL) assert(ag->ag_target != NULL); else assert(ag->ag_target == NULL); found = false; TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { if (targ->t_auth_group == ag) { found = true; break; } } TAILQ_FOREACH(port, &conf->conf_ports, p_next) { if (port->p_auth_group == ag) { found = true; break; } } TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { if (pg->pg_discovery_auth_group == ag) { found = true; break; } } if (!found && ag->ag_name != NULL && strcmp(ag->ag_name, "default") != 0 && strcmp(ag->ag_name, "no-authentication") != 0 && strcmp(ag->ag_name, "no-access") != 0) { log_warnx("auth-group \"%s\" not assigned " "to any target", ag->ag_name); } } return (true); } static bool portal_reuse_socket(struct portal *oldp, struct portal *newp) { struct kevent kev; if (strcmp(newp->p_listen, oldp->p_listen) != 0) return (false); if (oldp->p_socket <= 0) return (false); EV_SET(&kev, oldp->p_socket, EVFILT_READ, EV_ADD, 0, 0, newp); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1) return (false); newp->p_socket = oldp->p_socket; oldp->p_socket = 0; return (true); } static bool portal_init_socket(struct portal *p) { struct portal_group *pg = p->p_portal_group; struct kevent kev; int error, sockbuf; int one = 1; log_debugx("listening on %s, portal-group \"%s\"", p->p_listen, pg->pg_name); p->p_socket = socket(p->p_ai->ai_family, p->p_ai->ai_socktype, p->p_ai->ai_protocol); if (p->p_socket < 0) { log_warn("socket(2) failed for %s", p->p_listen); return (false); } sockbuf = SOCKBUF_SIZE; if (setsockopt(p->p_socket, SOL_SOCKET, SO_RCVBUF, &sockbuf, sizeof(sockbuf)) == -1) log_warn("setsockopt(SO_RCVBUF) failed for %s", p->p_listen); sockbuf = SOCKBUF_SIZE; if (setsockopt(p->p_socket, SOL_SOCKET, SO_SNDBUF, &sockbuf, sizeof(sockbuf)) == -1) log_warn("setsockopt(SO_SNDBUF) failed for %s", p->p_listen); if (setsockopt(p->p_socket, SOL_SOCKET, SO_NO_DDP, &one, sizeof(one)) == -1) log_warn("setsockopt(SO_NO_DDP) failed for %s", p->p_listen); error = setsockopt(p->p_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if (error != 0) { log_warn("setsockopt(SO_REUSEADDR) failed for %s", p->p_listen); close(p->p_socket); p->p_socket = 0; return (false); } if (pg->pg_dscp != -1) { /* Only allow the 6-bit DSCP field to be modified */ int tos = pg->pg_dscp << 2; switch (p->p_ai->ai_family) { case AF_INET: if (setsockopt(p->p_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IP_TOS) failed for %s", p->p_listen); break; case AF_INET6: if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IPV6_TCLASS) failed for %s", p->p_listen); break; } } if (pg->pg_pcp != -1) { int pcp = pg->pg_pcp; switch (p->p_ai->ai_family) { case AF_INET: if (setsockopt(p->p_socket, IPPROTO_IP, IP_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IP_VLAN_PCP) failed for %s", p->p_listen); break; case AF_INET6: if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IPV6_VLAN_PCP) failed for %s", p->p_listen); break; } } error = bind(p->p_socket, p->p_ai->ai_addr, p->p_ai->ai_addrlen); if (error != 0) { log_warn("bind(2) failed for %s", p->p_listen); close(p->p_socket); p->p_socket = 0; return (false); } error = listen(p->p_socket, -1); if (error != 0) { log_warn("listen(2) failed for %s", p->p_listen); close(p->p_socket); p->p_socket = 0; return (false); } EV_SET(&kev, p->p_socket, EVFILT_READ, EV_ADD, 0, 0, p); error = kevent(kqfd, &kev, 1, NULL, 0, NULL); if (error == -1) { log_warn("kevent(2) failed to register for %s", p->p_listen); close(p->p_socket); p->p_socket = 0; return (false); } return (true); } static int conf_apply(struct conf *oldconf, struct conf *newconf) { struct lun *oldlun, *newlun, *tmplun; struct portal_group *oldpg, *newpg; struct portal *oldp, *newp; struct port *oldport, *newport, *tmpport; struct isns *oldns, *newns; int changed, cumulated_error = 0, error; if (oldconf->conf_debug != newconf->conf_debug) { log_debugx("changing debug level to %d", newconf->conf_debug); log_init(newconf->conf_debug); } if (oldconf->conf_pidfile_path != NULL && newconf->conf_pidfile_path != NULL) { if (strcmp(oldconf->conf_pidfile_path, newconf->conf_pidfile_path) != 0) { /* pidfile has changed. rename it */ log_debugx("moving pidfile to %s", newconf->conf_pidfile_path); if (rename(oldconf->conf_pidfile_path, newconf->conf_pidfile_path)) { log_err(1, "renaming pidfile %s -> %s", oldconf->conf_pidfile_path, newconf->conf_pidfile_path); } } newconf->conf_pidfh = oldconf->conf_pidfh; oldconf->conf_pidfh = NULL; } /* * Go through the new portal groups, assigning tags or preserving old. */ TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { if (newpg->pg_tag != 0) continue; oldpg = portal_group_find(oldconf, newpg->pg_name); if (oldpg != NULL) newpg->pg_tag = oldpg->pg_tag; else newpg->pg_tag = ++last_portal_group_tag; } /* Deregister on removed iSNS servers. */ TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) { TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) { if (strcmp(oldns->i_addr, newns->i_addr) == 0) break; } if (newns == NULL) isns_deregister(oldns); } /* * XXX: If target or lun removal fails, we should somehow "move" * the old lun or target into newconf, 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. */ TAILQ_FOREACH_SAFE(oldport, &oldconf->conf_ports, p_next, tmpport) { if (port_is_dummy(oldport)) continue; newport = port_find(newconf, oldport->p_name); if (newport != NULL && !port_is_dummy(newport)) continue; log_debugx("removing port \"%s\"", oldport->p_name); error = kernel_port_remove(oldport); if (error != 0) { log_warnx("failed to remove port %s", oldport->p_name); /* * XXX: Uncomment after fixing the root cause. * * cumulated_error++; */ } } /* * Second, remove any LUNs present in the old configuration * and missing in the new one. */ TAILQ_FOREACH_SAFE(oldlun, &oldconf->conf_luns, l_next, tmplun) { newlun = lun_find(newconf, oldlun->l_name); if (newlun == NULL) { log_debugx("lun \"%s\", CTL lun %d " "not found in new configuration; " "removing", oldlun->l_name, oldlun->l_ctl_lun); error = kernel_lun_remove(oldlun); if (error != 0) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", oldlun->l_name, oldlun->l_ctl_lun); cumulated_error++; } continue; } /* * Also remove the LUNs changed by more than size. */ changed = 0; assert(oldlun->l_backend != NULL); assert(newlun->l_backend != NULL); if (strcmp(newlun->l_backend, oldlun->l_backend) != 0) { log_debugx("backend for lun \"%s\", " "CTL lun %d changed; removing", oldlun->l_name, oldlun->l_ctl_lun); changed = 1; } if (oldlun->l_blocksize != newlun->l_blocksize) { log_debugx("blocksize for lun \"%s\", " "CTL lun %d changed; removing", oldlun->l_name, oldlun->l_ctl_lun); changed = 1; } if (newlun->l_device_id != NULL && (oldlun->l_device_id == NULL || strcmp(oldlun->l_device_id, newlun->l_device_id) != 0)) { log_debugx("device-id for lun \"%s\", " "CTL lun %d changed; removing", oldlun->l_name, oldlun->l_ctl_lun); changed = 1; } if (newlun->l_path != NULL && (oldlun->l_path == NULL || strcmp(oldlun->l_path, newlun->l_path) != 0)) { log_debugx("path for lun \"%s\", " "CTL lun %d, changed; removing", oldlun->l_name, oldlun->l_ctl_lun); changed = 1; } if (newlun->l_serial != NULL && (oldlun->l_serial == NULL || strcmp(oldlun->l_serial, newlun->l_serial) != 0)) { log_debugx("serial for lun \"%s\", " "CTL lun %d changed; removing", oldlun->l_name, oldlun->l_ctl_lun); changed = 1; } if (changed) { error = kernel_lun_remove(oldlun); if (error != 0) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", oldlun->l_name, oldlun->l_ctl_lun); cumulated_error++; } lun_delete(oldlun); continue; } newlun->l_ctl_lun = oldlun->l_ctl_lun; } TAILQ_FOREACH_SAFE(newlun, &newconf->conf_luns, l_next, tmplun) { oldlun = lun_find(oldconf, newlun->l_name); if (oldlun != NULL) { log_debugx("modifying lun \"%s\", CTL lun %d", newlun->l_name, newlun->l_ctl_lun); error = kernel_lun_modify(newlun); if (error != 0) { log_warnx("failed to " "modify lun \"%s\", CTL lun %d", newlun->l_name, newlun->l_ctl_lun); cumulated_error++; } continue; } log_debugx("adding lun \"%s\"", newlun->l_name); error = kernel_lun_add(newlun); if (error != 0) { log_warnx("failed to add lun \"%s\"", newlun->l_name); lun_delete(newlun); cumulated_error++; } } /* * Now add new ports or modify existing ones. */ TAILQ_FOREACH_SAFE(newport, &newconf->conf_ports, p_next, tmpport) { if (port_is_dummy(newport)) continue; oldport = port_find(oldconf, newport->p_name); if (oldport == NULL || port_is_dummy(oldport)) { log_debugx("adding port \"%s\"", newport->p_name); error = kernel_port_add(newport); } else { log_debugx("updating port \"%s\"", newport->p_name); newport->p_ctl_port = oldport->p_ctl_port; error = kernel_port_update(newport, oldport); } if (error != 0) { log_warnx("failed to %s port %s", (oldport == NULL) ? "add" : "update", newport->p_name); if (oldport == NULL || port_is_dummy(oldport)) port_delete(newport); /* * XXX: Uncomment after fixing the root cause. * * cumulated_error++; */ } } /* * Go through the new portals, opening the sockets as necessary. */ TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { if (newpg->pg_foreign) continue; if (newpg->pg_unassigned) { log_debugx("not listening on portal-group \"%s\", " "not assigned to any target", newpg->pg_name); continue; } TAILQ_FOREACH(newp, &newpg->pg_portals, p_next) { /* * 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. */ TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) { TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) { if (portal_reuse_socket(oldp, newp)) goto reused; } } reused: if (newp->p_socket > 0) { /* * We're done with this portal. */ continue; } #ifdef ICL_KERNEL_PROXY if (proxy_mode) { newpg->pg_conf->conf_portal_id++; newp->p_id = newpg->pg_conf->conf_portal_id; log_debugx("listening on %s, portal-group " "\"%s\", portal id %d, using ICL proxy", newp->p_listen, newpg->pg_name, newp->p_id); kernel_listen(newp->p_ai, newp->p_iser, newp->p_id); continue; } #endif assert(proxy_mode == false); assert(newp->p_iser == false); if (!portal_init_socket(newp)) { cumulated_error++; continue; } } } /* * Go through the no longer used sockets, closing them. */ TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) { TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) { if (oldp->p_socket <= 0) continue; log_debugx("closing socket for %s, portal-group \"%s\"", oldp->p_listen, oldpg->pg_name); close(oldp->p_socket); oldp->p_socket = 0; } } /* (Re-)Register on remaining/new iSNS servers. */ TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) { TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) { if (strcmp(oldns->i_addr, newns->i_addr) == 0) break; } isns_register(newns, oldns); } /* Schedule iSNS update */ if (!TAILQ_EMPTY(&newconf->conf_isns)) set_timeout((newconf->conf_isns_period + 2) / 3, false); return (cumulated_error); } static 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 set_timeout(int timeout, int fatal) { struct sigaction sa; struct itimerval itv; int error; if (timeout <= 0) { 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; 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 conf_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, const struct sockaddr *client_sa, bool dont_fork) { struct ctld_connection *conn; int error; pid_t pid; char host[NI_MAXHOST + 1]; struct conf *conf; conf = portal->p_portal_group->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->conf_maxproc > 0 && nchildren >= conf->conf_maxproc) { log_debugx("maxproc limit of %d child processes hit; " "waiting for child process to exit", conf->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); return; } pidfile_close(conf->conf_pidfh); } 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, portal->p_portal_group->pg_name); log_set_peer_addr(host); setproctitle("%s", host); conn = connection_new(portal, fd, host, client_sa); set_timeout(conf->conf_timeout, true); kernel_capsicate(); login(conn); if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { kernel_handoff(conn); log_debugx("connection handed off to the kernel"); } else { assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); discovery(conn); } 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); TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { if (portal->p_id == portal_id) { goto found; } } } log_errx(1, "kernel returned invalid portal_id %d", portal_id); found: 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 = kev.udata; + portal = reinterpret_cast(kev.udata); assert(portal->p_socket == (int)kev.ident); client_salen = sizeof(client_sa); client_fd = accept(portal->p_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 struct conf * conf_new_from_file(const char *path, bool ucl) { struct conf *conf; struct auth_group *ag; struct portal_group *pg; bool valid; log_debugx("obtaining configuration from %s", path); conf = conf_new(); ag = auth_group_new(conf, "default"); assert(ag != NULL); ag = auth_group_new(conf, "no-authentication"); assert(ag != NULL); ag->ag_type = AG_TYPE_NO_AUTHENTICATION; ag = auth_group_new(conf, "no-access"); assert(ag != NULL); ag->ag_type = AG_TYPE_DENY; pg = portal_group_new(conf, "default"); assert(pg != NULL); conf_start(conf); if (ucl) valid = uclparse_conf(path); else valid = parse_conf(path); conf_finish(); if (!valid) { conf_delete(conf); return (NULL); } check_perms(path); if (conf->conf_default_ag_defined == false) { log_debugx("auth-group \"default\" not defined; " "going with defaults"); ag = auth_group_find(conf, "default"); assert(ag != NULL); ag->ag_type = AG_TYPE_DENY; } if (conf->conf_default_pg_defined == false) { log_debugx("portal-group \"default\" not defined; " "going with defaults"); pg = portal_group_find(conf, "default"); assert(pg != NULL); portal_group_add_portal(pg, "0.0.0.0", false); portal_group_add_portal(pg, "[::]", false); } conf->conf_kernel_port_on = true; if (!conf_verify(conf)) { conf_delete(conf); return (NULL); } return (conf); } /* * If the config file specifies physical ports for any target, associate them * with the config file. If necessary, create them. */ static bool new_pports_from_conf(struct conf *conf, struct kports *kports) { struct target *targ; struct pport *pp; struct port *tp; int ret, i_pp, i_vp; TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { if (!targ->t_pport) continue; ret = sscanf(targ->t_pport, "ioctl/%d/%d", &i_pp, &i_vp); if (ret > 0) { tp = port_new_ioctl(conf, kports, targ, i_pp, i_vp); if (tp == NULL) { log_warnx("can't create new ioctl port " "for target \"%s\"", targ->t_name); return (false); } continue; } pp = pport_find(kports, targ->t_pport); if (pp == NULL) { log_warnx("unknown port \"%s\" for target \"%s\"", targ->t_pport, targ->t_name); return (false); } if (!TAILQ_EMPTY(&pp->pp_ports)) { log_warnx("can't link port \"%s\" to target \"%s\", " "port already linked to some target", targ->t_pport, targ->t_name); return (false); } tp = port_new_pp(conf, targ, pp); if (tp == NULL) { log_warnx("can't link port \"%s\" to target \"%s\"", targ->t_pport, targ->t_name); return (false); } } return (true); } int main(int argc, char **argv) { struct kports kports; struct conf *oldconf, *newconf, *tmpconf; struct isns *newns; const char *config_path = DEFAULT_CONFIG_PATH; int debug = 0, ch, error; pid_t otherpid; 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(); TAILQ_INIT(&kports.pports); newconf = conf_new_from_file(config_path, use_ucl); if (newconf == NULL) log_errx(1, "configuration error; exiting"); if (test_config) return (0); assert(newconf->conf_pidfile_path != NULL); log_debugx("opening pidfile %s", newconf->conf_pidfile_path); newconf->conf_pidfh = pidfile_open(newconf->conf_pidfile_path, 0600, &otherpid); if (newconf->conf_pidfh == NULL) { if (errno == EEXIST) log_errx(1, "daemon already running, pid: %jd.", (intmax_t)otherpid); log_err(1, "cannot open or create pidfile \"%s\"", newconf->conf_pidfile_path); } register_signals(); oldconf = conf_new_from_kernel(&kports); if (debug > 0) { oldconf->conf_debug = debug; newconf->conf_debug = debug; } if (!new_pports_from_conf(newconf, &kports)) log_errx(1, "Error associating physical ports; exiting"); if (daemonize) { log_debugx("daemonizing"); if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); pidfile_remove(newconf->conf_pidfh); exit(1); } } kqfd = kqueue(); if (kqfd == -1) { log_warn("Cannot create kqueue"); pidfile_remove(newconf->conf_pidfh); exit(1); } error = conf_apply(oldconf, newconf); if (error != 0) log_errx(1, "failed to apply configuration; exiting"); conf_delete(oldconf); oldconf = NULL; pidfile_write(newconf->conf_pidfh); /* Schedule iSNS update */ if (!TAILQ_EMPTY(&newconf->conf_isns)) set_timeout((newconf->conf_isns_period + 2) / 3, false); for (;;) { main_loop(!daemonize); if (sighup_received) { sighup_received = false; log_debugx("received SIGHUP, reloading configuration"); tmpconf = conf_new_from_file(config_path, use_ucl); if (tmpconf == NULL) { log_warnx("configuration error, " "continuing with old configuration"); } else if (new_pports_from_conf(tmpconf, &kports)) { log_warnx("Error associating physical ports, " "continuing with old configuration"); conf_delete(tmpconf); } else { if (debug > 0) tmpconf->conf_debug = debug; oldconf = newconf; newconf = tmpconf; error = conf_apply(oldconf, newconf); if (error != 0) log_warnx("failed to reload " "configuration"); conf_delete(oldconf); oldconf = NULL; } } else if (sigterm_received) { log_debugx("exiting on signal; " "reloading empty configuration"); log_debugx("removing CTL iSCSI ports " "and terminating all connections"); oldconf = newconf; newconf = conf_new(); if (debug > 0) newconf->conf_debug = debug; error = conf_apply(oldconf, newconf); if (error != 0) log_warnx("failed to apply configuration"); if (oldconf->conf_pidfh) { pidfile_remove(oldconf->conf_pidfh); oldconf->conf_pidfh = NULL; } conf_delete(newconf); conf_delete(oldconf); oldconf = NULL; log_warnx("exiting on signal"); exit(0); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); if (timed_out()) { set_timeout(0, false); TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) isns_check(newns); /* Schedule iSNS update */ if (!TAILQ_EMPTY(&newconf->conf_isns)) { set_timeout((newconf->conf_isns_period + 2) / 3, false); } } } } /* NOTREACHED */ } diff --git a/usr.sbin/ctld/discovery.c b/usr.sbin/ctld/discovery.cc similarity index 100% rename from usr.sbin/ctld/discovery.c rename to usr.sbin/ctld/discovery.cc diff --git a/usr.sbin/ctld/isns.c b/usr.sbin/ctld/isns.cc similarity index 96% rename from usr.sbin/ctld/isns.c rename to usr.sbin/ctld/isns.cc index 0b836324ff47..e4d3744b84dc 100644 --- a/usr.sbin/ctld/isns.c +++ b/usr.sbin/ctld/isns.cc @@ -1,251 +1,251 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Alexander Motin * All rights reserved. * * 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 "ctld.h" #include "isns.h" struct isns_req * isns_req_alloc(void) { struct isns_req *req; - req = calloc(1, sizeof(struct isns_req)); + req = reinterpret_cast(calloc(1, sizeof(struct isns_req))); if (req == NULL) { log_err(1, "calloc"); return (NULL); } req->ir_buflen = sizeof(struct isns_hdr); req->ir_usedlen = 0; - req->ir_buf = calloc(1, req->ir_buflen); + req->ir_buf = reinterpret_cast(calloc(1, req->ir_buflen)); if (req->ir_buf == NULL) { free(req); log_err(1, "calloc"); return (NULL); } return (req); } struct isns_req * isns_req_create(uint16_t func, uint16_t flags) { struct isns_req *req; struct isns_hdr *hdr; req = isns_req_alloc(); req->ir_usedlen = sizeof(struct isns_hdr); hdr = (struct isns_hdr *)req->ir_buf; be16enc(hdr->ih_version, ISNS_VERSION); be16enc(hdr->ih_function, func); be16enc(hdr->ih_flags, flags); return (req); } void isns_req_free(struct isns_req *req) { free(req->ir_buf); free(req); } static int isns_req_getspace(struct isns_req *req, uint32_t len) { void *newbuf; int newlen; if (req->ir_usedlen + len <= req->ir_buflen) return (0); newlen = 1 << flsl(req->ir_usedlen + len); newbuf = realloc(req->ir_buf, newlen); if (newbuf == NULL) { log_err(1, "realloc"); return (1); } - req->ir_buf = newbuf; + req->ir_buf = reinterpret_cast(newbuf); req->ir_buflen = newlen; return (0); } void isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len, const void *value) { struct isns_tlv *tlv; uint32_t vlen; vlen = len + ((len & 3) ? (4 - (len & 3)) : 0); isns_req_getspace(req, sizeof(*tlv) + vlen); tlv = (struct isns_tlv *)&req->ir_buf[req->ir_usedlen]; be32enc(tlv->it_tag, tag); be32enc(tlv->it_length, vlen); memcpy(tlv->it_value, value, len); if (vlen != len) memset(&tlv->it_value[len], 0, vlen - len); req->ir_usedlen += sizeof(*tlv) + vlen; } void isns_req_add_delim(struct isns_req *req) { isns_req_add(req, 0, 0, NULL); } void isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value) { isns_req_add(req, tag, strlen(value) + 1, value); } void isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value) { uint32_t beval; be32enc(&beval, value); isns_req_add(req, tag, sizeof(value), &beval); } void isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai) { struct sockaddr_in *in4; struct sockaddr_in6 *in6; uint8_t buf[16]; switch (ai->ai_addr->sa_family) { case AF_INET: in4 = (struct sockaddr_in *)(void *)ai->ai_addr; memset(buf, 0, 10); buf[10] = 0xff; buf[11] = 0xff; memcpy(&buf[12], &in4->sin_addr, sizeof(in4->sin_addr)); isns_req_add(req, tag, sizeof(buf), buf); break; case AF_INET6: in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr; isns_req_add(req, tag, sizeof(in6->sin6_addr), &in6->sin6_addr); break; default: log_errx(1, "Unsupported address family %d", ai->ai_addr->sa_family); } } void isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai) { struct sockaddr_in *in4; struct sockaddr_in6 *in6; uint32_t buf; switch (ai->ai_addr->sa_family) { case AF_INET: in4 = (struct sockaddr_in *)(void *)ai->ai_addr; be32enc(&buf, ntohs(in4->sin_port)); isns_req_add(req, tag, sizeof(buf), &buf); break; case AF_INET6: in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr; be32enc(&buf, ntohs(in6->sin6_port)); isns_req_add(req, tag, sizeof(buf), &buf); break; default: log_errx(1, "Unsupported address family %d", ai->ai_addr->sa_family); } } int isns_req_send(int s, struct isns_req *req) { struct isns_hdr *hdr; int res; hdr = (struct isns_hdr *)req->ir_buf; be16enc(hdr->ih_length, req->ir_usedlen - sizeof(*hdr)); be16enc(hdr->ih_flags, be16dec(hdr->ih_flags) | ISNS_FLAG_LAST | ISNS_FLAG_FIRST); be16enc(hdr->ih_transaction, 0); be16enc(hdr->ih_sequence, 0); res = write(s, req->ir_buf, req->ir_usedlen); return ((res < 0) ? -1 : 0); } int isns_req_receive(int s, struct isns_req *req) { struct isns_hdr *hdr; ssize_t res, len; req->ir_usedlen = 0; isns_req_getspace(req, sizeof(*hdr)); res = read(s, req->ir_buf, sizeof(*hdr)); if (res < (ssize_t)sizeof(*hdr)) return (-1); req->ir_usedlen = sizeof(*hdr); hdr = (struct isns_hdr *)req->ir_buf; if (be16dec(hdr->ih_version) != ISNS_VERSION) return (-1); if ((be16dec(hdr->ih_flags) & (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) != (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) return (-1); len = be16dec(hdr->ih_length); isns_req_getspace(req, len); res = read(s, &req->ir_buf[req->ir_usedlen], len); if (res < len) return (-1); req->ir_usedlen += len; return (0); } uint32_t isns_req_get_status(struct isns_req *req) { if (req->ir_usedlen < sizeof(struct isns_hdr) + 4) return (-1); return (be32dec(&req->ir_buf[sizeof(struct isns_hdr)])); } diff --git a/usr.sbin/ctld/kernel.c b/usr.sbin/ctld/kernel.cc similarity index 98% rename from usr.sbin/ctld/kernel.c rename to usr.sbin/ctld/kernel.cc index 06ca99ce9d1f..9ddf22b9f4e0 100644 --- a/usr.sbin/ctld/kernel.c +++ b/usr.sbin/ctld/kernel.cc @@ -1,1265 +1,1265 @@ /*- * 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. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctld.h" #ifdef ICL_KERNEL_PROXY #include #endif #define NVLIST_BUFSIZE 1024 extern bool proxy_mode; int ctl_fd = 0; void kernel_init(void) { int retval, saved_errno; ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); if (ctl_fd < 0 && errno == ENOENT) { saved_errno = errno; retval = kldload("ctl"); if (retval != -1) ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); else errno = saved_errno; } if (ctl_fd < 0) log_err(1, "failed to open %s", CTL_DEFAULT_DEV); #ifdef WANT_ISCSI else { saved_errno = errno; if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1) log_warn("couldn't load cfiscsi"); errno = saved_errno; } #endif } /* * Backend LUN information. */ struct cctl_lun { uint64_t lun_id; char *backend_type; uint8_t device_type; uint64_t size_blocks; uint32_t blocksize; char *serial_number; char *device_id; char *ctld_name; nvlist_t *attr_list; STAILQ_ENTRY(cctl_lun) links; }; struct cctl_port { uint32_t port_id; char *port_frontend; char *port_name; int pp; int vp; int cfiscsi_state; char *cfiscsi_target; uint16_t cfiscsi_portal_group_tag; char *ctld_portal_group_name; nvlist_t *attr_list; STAILQ_ENTRY(cctl_port) links; }; struct cctl_devlist_data { int num_luns; STAILQ_HEAD(,cctl_lun) lun_list; struct cctl_lun *cur_lun; int num_ports; STAILQ_HEAD(,cctl_port) port_list; struct cctl_port *cur_port; int level; struct sbuf *cur_sb[32]; }; static void cctl_start_element(void *user_data, const char *name, const char **attr) { int i; struct cctl_devlist_data *devlist; struct cctl_lun *cur_lun; devlist = (struct cctl_devlist_data *)user_data; cur_lun = devlist->cur_lun; devlist->level++; if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]))) log_errx(1, "%s: too many nesting levels, %zd max", __func__, nitems(devlist->cur_sb)); devlist->cur_sb[devlist->level] = sbuf_new_auto(); if (devlist->cur_sb[devlist->level] == NULL) log_err(1, "%s: unable to allocate sbuf", __func__); if (strcmp(name, "lun") == 0) { if (cur_lun != NULL) log_errx(1, "%s: improper lun element nesting", __func__); - cur_lun = calloc(1, sizeof(*cur_lun)); + cur_lun = reinterpret_cast(calloc(1, sizeof(*cur_lun))); if (cur_lun == NULL) log_err(1, "%s: cannot allocate %zd bytes", __func__, sizeof(*cur_lun)); devlist->num_luns++; devlist->cur_lun = cur_lun; cur_lun->attr_list = nvlist_create(0); STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links); for (i = 0; attr[i] != NULL; i += 2) { if (strcmp(attr[i], "id") == 0) { cur_lun->lun_id = strtoull(attr[i+1], NULL, 0); } else { log_errx(1, "%s: invalid LUN attribute %s = %s", __func__, attr[i], attr[i+1]); } } } } static void cctl_end_element(void *user_data, const char *name) { struct cctl_devlist_data *devlist; struct cctl_lun *cur_lun; char *str; int error; devlist = (struct cctl_devlist_data *)user_data; cur_lun = devlist->cur_lun; if ((cur_lun == NULL) && (strcmp(name, "ctllunlist") != 0)) log_errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name); if (devlist->cur_sb[devlist->level] == NULL) log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, devlist->level, name); sbuf_finish(devlist->cur_sb[devlist->level]); str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); if (strlen(str) == 0) { free(str); str = NULL; } sbuf_delete(devlist->cur_sb[devlist->level]); devlist->cur_sb[devlist->level] = NULL; devlist->level--; if (strcmp(name, "backend_type") == 0) { cur_lun->backend_type = str; str = NULL; } else if (strcmp(name, "lun_type") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_lun->device_type = strtoull(str, NULL, 0); } else if (strcmp(name, "size") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_lun->size_blocks = strtoull(str, NULL, 0); } else if (strcmp(name, "blocksize") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_lun->blocksize = strtoul(str, NULL, 0); } else if (strcmp(name, "serial_number") == 0) { cur_lun->serial_number = str; str = NULL; } else if (strcmp(name, "device_id") == 0) { cur_lun->device_id = str; str = NULL; } else if (strcmp(name, "ctld_name") == 0) { cur_lun->ctld_name = str; str = NULL; } else if (strcmp(name, "lun") == 0) { devlist->cur_lun = NULL; } else if (strcmp(name, "ctllunlist") == 0) { /* Nothing. */ } else { nvlist_move_string(cur_lun->attr_list, name, str); error = nvlist_error(cur_lun->attr_list); if (error != 0) log_errc(1, error, "%s: failed to add nv pair for %s", __func__, name); str = NULL; } free(str); } static void cctl_start_pelement(void *user_data, const char *name, const char **attr) { int i; struct cctl_devlist_data *devlist; struct cctl_port *cur_port; devlist = (struct cctl_devlist_data *)user_data; cur_port = devlist->cur_port; devlist->level++; if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]))) log_errx(1, "%s: too many nesting levels, %zd max", __func__, nitems(devlist->cur_sb)); devlist->cur_sb[devlist->level] = sbuf_new_auto(); if (devlist->cur_sb[devlist->level] == NULL) log_err(1, "%s: unable to allocate sbuf", __func__); if (strcmp(name, "targ_port") == 0) { if (cur_port != NULL) log_errx(1, "%s: improper port element nesting (%s)", __func__, name); - cur_port = calloc(1, sizeof(*cur_port)); + cur_port = reinterpret_cast(calloc(1, sizeof(*cur_port))); if (cur_port == NULL) log_err(1, "%s: cannot allocate %zd bytes", __func__, sizeof(*cur_port)); devlist->num_ports++; devlist->cur_port = cur_port; cur_port->attr_list = nvlist_create(0); STAILQ_INSERT_TAIL(&devlist->port_list, cur_port, links); for (i = 0; attr[i] != NULL; i += 2) { if (strcmp(attr[i], "id") == 0) { cur_port->port_id = strtoul(attr[i+1], NULL, 0); } else { log_errx(1, "%s: invalid LUN attribute %s = %s", __func__, attr[i], attr[i+1]); } } } } static void cctl_end_pelement(void *user_data, const char *name) { struct cctl_devlist_data *devlist; struct cctl_port *cur_port; char *str; int error; devlist = (struct cctl_devlist_data *)user_data; cur_port = devlist->cur_port; if ((cur_port == NULL) && (strcmp(name, "ctlportlist") != 0)) log_errx(1, "%s: cur_port == NULL! (name = %s)", __func__, name); if (devlist->cur_sb[devlist->level] == NULL) log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, devlist->level, name); sbuf_finish(devlist->cur_sb[devlist->level]); str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); if (strlen(str) == 0) { free(str); str = NULL; } sbuf_delete(devlist->cur_sb[devlist->level]); devlist->cur_sb[devlist->level] = NULL; devlist->level--; if (strcmp(name, "frontend_type") == 0) { cur_port->port_frontend = str; str = NULL; } else if (strcmp(name, "port_name") == 0) { cur_port->port_name = str; str = NULL; } else if (strcmp(name, "physical_port") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_port->pp = strtoul(str, NULL, 0); } else if (strcmp(name, "virtual_port") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_port->vp = strtoul(str, NULL, 0); } else if (strcmp(name, "cfiscsi_target") == 0) { cur_port->cfiscsi_target = str; str = NULL; } else if (strcmp(name, "cfiscsi_state") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_port->cfiscsi_state = strtoul(str, NULL, 0); } else if (strcmp(name, "cfiscsi_portal_group_tag") == 0) { if (str == NULL) log_errx(1, "%s: %s missing its argument", __func__, name); cur_port->cfiscsi_portal_group_tag = strtoul(str, NULL, 0); } else if (strcmp(name, "ctld_portal_group_name") == 0) { cur_port->ctld_portal_group_name = str; str = NULL; } else if (strcmp(name, "targ_port") == 0) { devlist->cur_port = NULL; } else if (strcmp(name, "ctlportlist") == 0) { /* Nothing. */ } else { nvlist_move_string(cur_port->attr_list, name, str); error = nvlist_error(cur_port->attr_list); if (error != 0) log_errc(1, error, "%s: failed to add nv pair for %s", __func__, name); str = NULL; } free(str); } static void cctl_char_handler(void *user_data, const XML_Char *str, int len) { struct cctl_devlist_data *devlist; devlist = (struct cctl_devlist_data *)user_data; sbuf_bcat(devlist->cur_sb[devlist->level], str, len); } struct conf * conf_new_from_kernel(struct kports *kports) { struct conf *conf = NULL; struct target *targ; struct portal_group *pg; struct pport *pp; struct port *cp; struct lun *cl; struct ctl_lun_list list; struct cctl_devlist_data devlist; struct cctl_lun *lun; struct cctl_port *port; XML_Parser parser; const char *key; char *str, *name; void *cookie; int error, len, retval; bzero(&devlist, sizeof(devlist)); STAILQ_INIT(&devlist.lun_list); STAILQ_INIT(&devlist.port_list); log_debugx("obtaining previously configured CTL luns from the kernel"); str = NULL; len = 4096; retry: - str = realloc(str, len); + str = reinterpret_cast(realloc(str, len)); if (str == NULL) log_err(1, "realloc"); bzero(&list, sizeof(list)); list.alloc_len = len; list.status = CTL_LUN_LIST_NONE; list.lun_xml = str; if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) { log_warn("error issuing CTL_LUN_LIST ioctl"); free(str); return (NULL); } if (list.status == CTL_LUN_LIST_ERROR) { log_warnx("error returned from CTL_LUN_LIST ioctl: %s", list.error_str); free(str); return (NULL); } if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { len = len << 1; goto retry; } parser = XML_ParserCreate(NULL); if (parser == NULL) { log_warnx("unable to create XML parser"); free(str); return (NULL); } XML_SetUserData(parser, &devlist); XML_SetElementHandler(parser, cctl_start_element, cctl_end_element); XML_SetCharacterDataHandler(parser, cctl_char_handler); retval = XML_Parse(parser, str, strlen(str), 1); XML_ParserFree(parser); free(str); if (retval != 1) { log_warnx("XML_Parse failed"); return (NULL); } str = NULL; len = 4096; retry_port: - str = realloc(str, len); + str = reinterpret_cast(realloc(str, len)); if (str == NULL) log_err(1, "realloc"); bzero(&list, sizeof(list)); list.alloc_len = len; list.status = CTL_LUN_LIST_NONE; list.lun_xml = str; if (ioctl(ctl_fd, CTL_PORT_LIST, &list) == -1) { log_warn("error issuing CTL_PORT_LIST ioctl"); free(str); return (NULL); } if (list.status == CTL_LUN_LIST_ERROR) { log_warnx("error returned from CTL_PORT_LIST ioctl: %s", list.error_str); free(str); return (NULL); } if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { len = len << 1; goto retry_port; } parser = XML_ParserCreate(NULL); if (parser == NULL) { log_warnx("unable to create XML parser"); free(str); return (NULL); } XML_SetUserData(parser, &devlist); XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement); XML_SetCharacterDataHandler(parser, cctl_char_handler); retval = XML_Parse(parser, str, strlen(str), 1); XML_ParserFree(parser); free(str); if (retval != 1) { log_warnx("XML_Parse failed"); return (NULL); } conf = conf_new(); name = NULL; STAILQ_FOREACH(port, &devlist.port_list, links) { if (strcmp(port->port_frontend, "ha") == 0) continue; free(name); if (port->pp == 0 && port->vp == 0) { name = checked_strdup(port->port_name); } else if (port->vp == 0) { retval = asprintf(&name, "%s/%d", port->port_name, port->pp); if (retval <= 0) log_err(1, "asprintf"); } else { retval = asprintf(&name, "%s/%d/%d", port->port_name, port->pp, port->vp); if (retval <= 0) log_err(1, "asprintf"); } if (port->cfiscsi_target == NULL) { log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ", port->port_id, name); pp = pport_find(kports, name); if (pp == NULL) { pp = pport_new(kports, name, port->port_id); if (pp == NULL) { log_warnx("pport_new failed"); continue; } } continue; } if (port->cfiscsi_state != 1) { log_debugx("CTL port %ju is not active (%d); ignoring", (uintmax_t)port->port_id, port->cfiscsi_state); continue; } targ = target_find(conf, port->cfiscsi_target); if (targ == NULL) { targ = target_new(conf, port->cfiscsi_target); if (targ == NULL) { log_warnx("target_new failed"); continue; } } if (port->ctld_portal_group_name == NULL) continue; pg = portal_group_find(conf, port->ctld_portal_group_name); if (pg == NULL) { pg = portal_group_new(conf, port->ctld_portal_group_name); if (pg == NULL) { log_warnx("portal_group_new failed"); continue; } } pg->pg_tag = port->cfiscsi_portal_group_tag; cp = port_new(conf, targ, pg); if (cp == NULL) { log_warnx("port_new failed"); continue; } cp->p_ctl_port = port->port_id; } while ((port = STAILQ_FIRST(&devlist.port_list))) { STAILQ_REMOVE_HEAD(&devlist.port_list, links); free(port->port_frontend); free(port->port_name); free(port->cfiscsi_target); free(port->ctld_portal_group_name); nvlist_destroy(port->attr_list); free(port); } free(name); STAILQ_FOREACH(lun, &devlist.lun_list, links) { if (lun->ctld_name == NULL) { log_debugx("CTL lun %ju wasn't managed by ctld; " "ignoring", (uintmax_t)lun->lun_id); continue; } cl = lun_find(conf, lun->ctld_name); if (cl != NULL) { log_warnx("found CTL lun %ju \"%s\", " "also backed by CTL lun %d; ignoring", (uintmax_t)lun->lun_id, lun->ctld_name, cl->l_ctl_lun); continue; } log_debugx("found CTL lun %ju \"%s\"", (uintmax_t)lun->lun_id, lun->ctld_name); cl = lun_new(conf, lun->ctld_name); if (cl == NULL) { log_warnx("lun_new failed"); continue; } cl->l_backend = lun->backend_type; lun->backend_type = NULL; cl->l_device_type = lun->device_type; cl->l_blocksize = lun->blocksize; cl->l_device_id = lun->device_id; lun->device_id = NULL; cl->l_serial = lun->serial_number; lun->serial_number = NULL; cl->l_size = lun->size_blocks * cl->l_blocksize; cl->l_ctl_lun = lun->lun_id; cookie = NULL; while ((key = nvlist_next(lun->attr_list, NULL, &cookie)) != NULL) { if (strcmp(key, "file") == 0 || strcmp(key, "dev") == 0) { cl->l_path = nvlist_take_string(lun->attr_list, key); continue; } nvlist_add_string(cl->l_options, key, nvlist_get_string(lun->attr_list, key)); error = nvlist_error(cl->l_options); if (error != 0) log_warnc(error, "unable to add CTL lun option " "%s for CTL lun %ju \"%s\"", key, (uintmax_t)lun->lun_id, cl->l_name); } } while ((lun = STAILQ_FIRST(&devlist.lun_list))) { STAILQ_REMOVE_HEAD(&devlist.lun_list, links); nvlist_destroy(lun->attr_list); free(lun); } return (conf); } 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); } int kernel_lun_add(struct lun *lun) { struct ctl_lun_req req; int error; bzero(&req, sizeof(req)); strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); req.reqtype = CTL_LUNREQ_CREATE; req.reqdata.create.blocksize_bytes = lun->l_blocksize; if (lun->l_size != 0) req.reqdata.create.lun_size_bytes = lun->l_size; if (lun->l_ctl_lun >= 0) { req.reqdata.create.req_lun_id = lun->l_ctl_lun; req.reqdata.create.flags |= CTL_LUN_FLAG_ID_REQ; } req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; req.reqdata.create.device_type = lun->l_device_type; if (lun->l_serial != NULL) { - strncpy(req.reqdata.create.serial_num, lun->l_serial, + strncpy((char *)req.reqdata.create.serial_num, lun->l_serial, sizeof(req.reqdata.create.serial_num)); req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM; } if (lun->l_device_id != NULL) { - strncpy(req.reqdata.create.device_id, lun->l_device_id, + strncpy((char *)req.reqdata.create.device_id, lun->l_device_id, sizeof(req.reqdata.create.device_id)); req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID; } if (lun->l_path != NULL) nvlist_replace_string(lun->l_options, "file", lun->l_path); nvlist_replace_string(lun->l_options, "ctld_name", lun->l_name); if (!nvlist_exists_string(lun->l_options, "scsiname") && lun->l_scsiname != NULL) nvlist_add_string(lun->l_options, "scsiname", lun->l_scsiname); if (!nvlist_empty(lun->l_options)) { req.args = nvlist_pack(lun->l_options, &req.args_len); if (req.args == NULL) { log_warn("error packing nvlist"); return (1); } } error = ioctl(ctl_fd, CTL_LUN_REQ, &req); free(req.args); if (error != 0) { log_warn("error issuing CTL_LUN_REQ ioctl"); return (1); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN creation error: %s", req.error_str); return (1); case CTL_LUN_WARNING: log_warnx("LUN creation warning: %s", req.error_str); break; case CTL_LUN_OK: break; default: log_warnx("unknown LUN creation status: %d", req.status); return (1); } lun->l_ctl_lun = req.reqdata.create.req_lun_id; return (0); } int kernel_lun_modify(struct lun *lun) { struct ctl_lun_req req; int error; bzero(&req, sizeof(req)); strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); req.reqtype = CTL_LUNREQ_MODIFY; req.reqdata.modify.lun_id = lun->l_ctl_lun; req.reqdata.modify.lun_size_bytes = lun->l_size; if (lun->l_path != NULL) nvlist_replace_string(lun->l_options, "file", lun->l_path); nvlist_replace_string(lun->l_options, "ctld_name", lun->l_name); if (!nvlist_exists_string(lun->l_options, "scsiname") && lun->l_scsiname != NULL) nvlist_add_string(lun->l_options, "scsiname", lun->l_scsiname); if (!nvlist_empty(lun->l_options)) { req.args = nvlist_pack(lun->l_options, &req.args_len); if (req.args == NULL) { log_warn("error packing nvlist"); return (1); } } error = ioctl(ctl_fd, CTL_LUN_REQ, &req); free(req.args); if (error != 0) { log_warn("error issuing CTL_LUN_REQ ioctl"); return (1); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN modification error: %s", req.error_str); return (1); case CTL_LUN_WARNING: log_warnx("LUN modification warning: %s", req.error_str); break; case CTL_LUN_OK: break; default: log_warnx("unknown LUN modification status: %d", req.status); return (1); } return (0); } int kernel_lun_remove(struct lun *lun) { struct ctl_lun_req req; bzero(&req, sizeof(req)); strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); req.reqtype = CTL_LUNREQ_RM; req.reqdata.rm.lun_id = lun->l_ctl_lun; if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { log_warn("error issuing CTL_LUN_REQ ioctl"); return (1); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN removal error: %s", req.error_str); return (1); case CTL_LUN_WARNING: log_warnx("LUN removal warning: %s", req.error_str); break; case CTL_LUN_OK: break; default: log_warnx("unknown LUN removal status: %d", req.status); return (1); } return (0); } void kernel_handoff(struct ctld_connection *conn) { struct ctl_iscsi req; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_HANDOFF; strlcpy(req.data.handoff.initiator_name, conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name)); strlcpy(req.data.handoff.initiator_addr, conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr)); if (conn->conn_initiator_alias != NULL) { strlcpy(req.data.handoff.initiator_alias, conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias)); } memcpy(req.data.handoff.initiator_isid, conn->conn_initiator_isid, sizeof(req.data.handoff.initiator_isid)); strlcpy(req.data.handoff.target_name, conn->conn_target->t_name, sizeof(req.data.handoff.target_name)); if (conn->conn_portal->p_portal_group->pg_offload != NULL) { strlcpy(req.data.handoff.offload, conn->conn_portal->p_portal_group->pg_offload, sizeof(req.data.handoff.offload)); } #ifdef ICL_KERNEL_PROXY if (proxy_mode) req.data.handoff.connection_id = conn->conn.conn_socket; else req.data.handoff.socket = conn->conn.conn_socket; #else req.data.handoff.socket = conn->conn.conn_socket; #endif req.data.handoff.portal_group_tag = conn->conn_portal->p_portal_group->pg_tag; if (conn->conn.conn_header_digest == CONN_DIGEST_CRC32C) req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; if (conn->conn.conn_data_digest == CONN_DIGEST_CRC32C) req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; req.data.handoff.cmdsn = conn->conn.conn_cmdsn; req.data.handoff.statsn = conn->conn.conn_statsn; req.data.handoff.max_recv_data_segment_length = conn->conn.conn_max_recv_data_segment_length; req.data.handoff.max_send_data_segment_length = conn->conn.conn_max_send_data_segment_length; req.data.handoff.max_burst_length = conn->conn.conn_max_burst_length; req.data.handoff.first_burst_length = conn->conn.conn_first_burst_length; req.data.handoff.immediate_data = conn->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); } } int kernel_port_add(struct port *port) { struct ctl_port_entry entry; struct ctl_req req; struct ctl_lun_map lm; struct target *targ = port->p_target; struct portal_group *pg = port->p_portal_group; char result_buf[NVLIST_BUFSIZE]; int error, i; /* Create iSCSI port. */ if (port->p_portal_group || port->p_ioctl_port) { bzero(&req, sizeof(req)); req.reqtype = CTL_REQ_CREATE; if (port->p_portal_group) { strlcpy(req.driver, "iscsi", sizeof(req.driver)); req.args_nvl = nvlist_clone(pg->pg_options); nvlist_add_string(req.args_nvl, "cfiscsi_target", targ->t_name); nvlist_add_string(req.args_nvl, "ctld_portal_group_name", pg->pg_name); nvlist_add_stringf(req.args_nvl, "cfiscsi_portal_group_tag", "%u", pg->pg_tag); if (targ->t_alias) { nvlist_add_string(req.args_nvl, "cfiscsi_target_alias", targ->t_alias); } } if (port->p_ioctl_port) { strlcpy(req.driver, "ioctl", sizeof(req.driver)); req.args_nvl = nvlist_create(0); nvlist_add_stringf(req.args_nvl, "pp", "%d", port->p_ioctl_pp); nvlist_add_stringf(req.args_nvl, "vp", "%d", port->p_ioctl_vp); } req.args = nvlist_pack(req.args_nvl, &req.args_len); if (req.args == NULL) { nvlist_destroy(req.args_nvl); log_warn("error packing nvlist"); return (1); } req.result = result_buf; req.result_len = sizeof(result_buf); error = ioctl(ctl_fd, CTL_PORT_REQ, &req); free(req.args); nvlist_destroy(req.args_nvl); if (error != 0) { log_warn("error issuing CTL_PORT_REQ ioctl"); return (1); } if (req.status == CTL_LUN_ERROR) { log_warnx("error returned from port creation request: %s", req.error_str); return (1); } if (req.status != CTL_LUN_OK) { log_warnx("unknown port creation request status %d", req.status); return (1); } req.result_nvl = nvlist_unpack(result_buf, req.result_len, 0); if (req.result_nvl == NULL) { log_warnx("error unpacking result nvlist"); return (1); } port->p_ctl_port = nvlist_get_number(req.result_nvl, "port_id"); nvlist_destroy(req.result_nvl); } else if (port->p_pport) { port->p_ctl_port = port->p_pport->pp_ctl_port; if (strncmp(targ->t_name, "naa.", 4) == 0 && strlen(targ->t_name) == 20) { bzero(&entry, sizeof(entry)); entry.port_type = CTL_PORT_NONE; entry.targ_port = port->p_ctl_port; entry.flags |= CTL_PORT_WWNN_VALID; entry.wwnn = strtoull(targ->t_name + 4, NULL, 16); if (ioctl(ctl_fd, CTL_SET_PORT_WWNS, &entry) == -1) log_warn("CTL_SET_PORT_WWNS ioctl failed"); } } /* Explicitly enable mapping to block any access except allowed. */ lm.port = port->p_ctl_port; lm.plun = UINT32_MAX; lm.lun = 0; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); /* Map configured LUNs */ for (i = 0; i < MAX_LUNS; i++) { if (targ->t_luns[i] == NULL) continue; lm.port = port->p_ctl_port; lm.plun = i; lm.lun = targ->t_luns[i]->l_ctl_lun; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); } /* Enable port */ bzero(&entry, sizeof(entry)); entry.targ_port = port->p_ctl_port; error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry); if (error != 0) { log_warn("CTL_ENABLE_PORT ioctl failed"); return (-1); } return (0); } int kernel_port_update(struct port *port, struct port *oport) { struct ctl_lun_map lm; struct target *targ = port->p_target; struct target *otarg = oport->p_target; int error, i; uint32_t olun; /* Map configured LUNs and unmap others */ for (i = 0; i < MAX_LUNS; i++) { lm.port = port->p_ctl_port; lm.plun = i; if (targ->t_luns[i] == NULL) lm.lun = UINT32_MAX; else lm.lun = targ->t_luns[i]->l_ctl_lun; if (otarg->t_luns[i] == NULL) olun = UINT32_MAX; else olun = otarg->t_luns[i]->l_ctl_lun; if (lm.lun == olun) continue; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); } return (0); } int kernel_port_remove(struct port *port) { struct ctl_port_entry entry; struct ctl_lun_map lm; struct ctl_req req; struct target *targ = port->p_target; struct portal_group *pg = port->p_portal_group; int error; /* Disable port */ bzero(&entry, sizeof(entry)); entry.targ_port = port->p_ctl_port; error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); if (error != 0) { log_warn("CTL_DISABLE_PORT ioctl failed"); return (-1); } /* Remove iSCSI or ioctl port. */ if (port->p_portal_group || port->p_ioctl_port) { bzero(&req, sizeof(req)); strlcpy(req.driver, port->p_ioctl_port ? "ioctl" : "iscsi", sizeof(req.driver)); req.reqtype = CTL_REQ_REMOVE; req.args_nvl = nvlist_create(0); if (req.args_nvl == NULL) log_err(1, "nvlist_create"); if (port->p_ioctl_port) nvlist_add_stringf(req.args_nvl, "port_id", "%d", port->p_ctl_port); else { nvlist_add_string(req.args_nvl, "cfiscsi_target", targ->t_name); nvlist_add_stringf(req.args_nvl, "cfiscsi_portal_group_tag", "%u", pg->pg_tag); } req.args = nvlist_pack(req.args_nvl, &req.args_len); if (req.args == NULL) { nvlist_destroy(req.args_nvl); log_warn("error packing nvlist"); return (1); } error = ioctl(ctl_fd, CTL_PORT_REQ, &req); free(req.args); nvlist_destroy(req.args_nvl); if (error != 0) { log_warn("error issuing CTL_PORT_REQ ioctl"); return (1); } if (req.status == CTL_LUN_ERROR) { log_warnx("error returned from port removal request: %s", req.error_str); return (1); } if (req.status != CTL_LUN_OK) { log_warnx("unknown port removal request status %d", req.status); return (1); } } else { /* Disable LUN mapping. */ lm.port = port->p_ctl_port; lm.plun = UINT32_MAX; lm.lun = UINT32_MAX; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); } return (0); } #ifdef ICL_KERNEL_PROXY void kernel_listen(struct addrinfo *ai, bool iser, int portal_id) { struct ctl_iscsi req; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_LISTEN; req.data.listen.iser = iser; req.data.listen.domain = ai->ai_family; req.data.listen.socktype = ai->ai_socktype; req.data.listen.protocol = ai->ai_protocol; req.data.listen.addr = ai->ai_addr; req.data.listen.addrlen = ai->ai_addrlen; req.data.listen.portal_id = portal_id; if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) log_err(1, "error issuing CTL_ISCSI ioctl"); if (req.status != CTL_ISCSI_OK) { log_errx(1, "error returned from CTL iSCSI listen: %s", req.error_str); } } void kernel_accept(int *connection_id, int *portal_id, struct sockaddr *client_sa, socklen_t *client_salen) { struct ctl_iscsi req; struct sockaddr_storage ss; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_ACCEPT; req.data.accept.initiator_addr = (struct sockaddr *)&ss; if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) log_err(1, "error issuing CTL_ISCSI ioctl"); if (req.status != CTL_ISCSI_OK) { log_errx(1, "error returned from CTL iSCSI accept: %s", req.error_str); } *connection_id = req.data.accept.connection_id; *portal_id = req.data.accept.portal_id; *client_salen = req.data.accept.initiator_addrlen; memcpy(client_sa, &ss, *client_salen); } void kernel_send(struct pdu *pdu) { struct ctl_iscsi req; bzero(&req, sizeof(req)); req.type = CTL_ISCSI_SEND; req.data.send.connection_id = pdu->pdu_connection->conn_socket; req.data.send.bhs = pdu->pdu_bhs; req.data.send.data_segment_len = pdu->pdu_data_len; req.data.send.data_segment = pdu->pdu_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 send: " "%s; dropping connection", req.error_str); } } void kernel_receive(struct pdu *pdu) { struct connection *conn; struct ctl_iscsi req; conn = pdu->pdu_connection; pdu->pdu_data = malloc(conn->conn_max_recv_data_segment_length); if (pdu->pdu_data == NULL) log_err(1, "malloc"); bzero(&req, sizeof(req)); req.type = CTL_ISCSI_RECEIVE; req.data.receive.connection_id = conn->conn_socket; req.data.receive.bhs = pdu->pdu_bhs; req.data.receive.data_segment_len = conn->conn_max_recv_data_segment_length; req.data.receive.data_segment = pdu->pdu_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 receive: " "%s; dropping connection", req.error_str); } } #endif /* ICL_KERNEL_PROXY */ /* * XXX: I CANT INTO LATIN */ void kernel_capsicate(void) { cap_rights_t rights; const unsigned long cmds[] = { CTL_ISCSI }; cap_rights_init(&rights, CAP_IOCTL); if (caph_rights_limit(ctl_fd, &rights) < 0) log_err(1, "cap_rights_limit"); if (caph_ioctls_limit(ctl_fd, cmds, nitems(cmds)) < 0) log_err(1, "cap_ioctls_limit"); if (caph_enter() < 0) log_err(1, "cap_enter"); if (cap_sandboxed()) log_debugx("Capsicum capability mode enabled"); else log_warnx("Capsicum capability mode not supported"); } diff --git a/usr.sbin/ctld/login.c b/usr.sbin/ctld/login.cc similarity index 99% rename from usr.sbin/ctld/login.c rename to usr.sbin/ctld/login.cc index 8833779c825f..84d329964ada 100644 --- a/usr.sbin/ctld/login.c +++ b/usr.sbin/ctld/login.cc @@ -1,1119 +1,1119 @@ /*- * 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.h" #include "iscsi_proto.h" #define MAX_DATA_SEGMENT_LENGTH (128 * 1024) static void login_send_error(struct pdu *request, - char class, char detail); + 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); if (offload != NULL) { 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 != NULL) { 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 class, char detail) +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", class, detail); + "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 = class; + 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) { 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->ag_type == AG_TYPE_CHAP || ag->ag_type == AG_TYPE_CHAP_MUTUAL); auth = auth_find(ag, chap_n); if (auth == NULL) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login with invalid user \"%s\"", chap_n); } assert(auth->a_secret != NULL); assert(strlen(auth->a_secret) > 0); error = chap_authenticate(chap, auth->a_secret); if (error != 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "CHAP authentication failed for user \"%s\"", auth->a_user); } keys_delete(request_keys); *authp = auth; return (request); } static void login_send_chap_success(struct pdu *request, const struct auth *auth) { 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->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator requests target authentication " "for user \"%s\", but mutual user/secret " "is not set", auth->a_user); } log_debugx("performing mutual authentication as user \"%s\"", auth->a_mutual_user); rchap = rchap_new(auth->a_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->a_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); } static void login_chap(struct ctld_connection *conn, struct auth_group *ag) { 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->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->conn, ag, chap, &auth); /* * Yay, authentication succeeded! */ log_debugx("authentication succeeded for user \"%s\"; " "transitioning to operational parameter negotiation", auth->a_user); login_send_chap_success(request, auth); pdu_delete(request); /* * Leave username and CHAP information for discovery(). */ conn->conn_user = auth->a_user; conn->conn_chap = chap; } static void login_negotiate_key(struct pdu *request, const char *name, const char *value, bool skipped_security, struct keys *response_keys) { int which; size_t tmp; struct ctld_connection *conn; conn = (struct ctld_connection *)request->pdu_connection; 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) { if (conn->conn_initiator_alias != NULL) free(conn->conn_initiator_alias); conn->conn_initiator_alias = checked_strdup(value); } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ } else if (strcmp(name, "HeaderDigest") == 0) { /* * We don't handle digests for discovery sessions. */ if (conn->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.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->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.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->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.conn_immediate_data = true; keys_add(response_keys, name, "Yes"); } else { conn->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->conn_max_send_data_segment_limit) { log_debugx("capping MaxRecvDataSegmentLength " "from %zd to %d", tmp, conn->conn_max_send_data_segment_limit); tmp = conn->conn_max_send_data_segment_limit; } conn->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->conn_max_burst_limit) { log_debugx("capping MaxBurstLength from %zd to %d", tmp, conn->conn_max_burst_limit); tmp = conn->conn_max_burst_limit; } conn->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->conn_first_burst_limit) { log_debugx("capping FirstBurstLength from %zd to %d", tmp, conn->conn_first_burst_limit); tmp = conn->conn_first_burst_limit; } conn->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); } static bool login_portal_redirect(struct ctld_connection *conn, struct pdu *request) { const struct portal_group *pg; pg = conn->conn_portal->p_portal_group; if (pg->pg_redirection == NULL) return (false); log_debugx("portal-group \"%s\" configured to redirect to %s", pg->pg_name, pg->pg_redirection); login_redirect(request, pg->pg_redirection); return (true); } static bool login_target_redirect(struct ctld_connection *conn, struct pdu *request) { const char *target_address; assert(conn->conn_portal->p_portal_group->pg_redirection == NULL); if (conn->conn_target == NULL) return (false); target_address = conn->conn_target->t_redirection; if (target_address == NULL) return (false); log_debugx("target \"%s\" configured to redirect to %s", conn->conn_target->t_name, target_address); login_redirect(request, target_address); return (true); } static void login_negotiate(struct ctld_connection *conn, struct pdu *request) { struct pdu *response; struct iscsi_bhs_login_response *bhslr2; struct keys *request_keys, *response_keys; int i; bool redirected, skipped_security; if (conn->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->conn_target != NULL); conn->conn_max_recv_data_segment_limit = (1 << 24) - 1; conn->conn_max_send_data_segment_limit = (1 << 24) - 1; conn->conn_max_burst_limit = (1 << 24) - 1; conn->conn_first_burst_limit = (1 << 24) - 1; kernel_limits(conn->conn_portal->p_portal_group->pg_offload, conn->conn.conn_socket, &conn->conn_max_recv_data_segment_limit, &conn->conn_max_send_data_segment_limit, &conn->conn_max_burst_limit, &conn->conn_first_burst_limit); /* We expect legal, usable values at this point. */ assert(conn->conn_max_recv_data_segment_limit >= 512); assert(conn->conn_max_recv_data_segment_limit < (1 << 24)); assert(conn->conn_max_send_data_segment_limit >= 512); assert(conn->conn_max_send_data_segment_limit < (1 << 24)); assert(conn->conn_max_burst_limit >= 512); assert(conn->conn_max_burst_limit < (1 << 24)); assert(conn->conn_first_burst_limit >= 512); assert(conn->conn_first_burst_limit < (1 << 24)); assert(conn->conn_first_burst_limit <= conn->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->conn_max_send_data_segment_limit < conn->conn.conn_max_send_data_segment_length) { conn->conn.conn_max_send_data_segment_length = conn->conn_max_send_data_segment_limit; } } else { conn->conn_max_recv_data_segment_limit = MAX_DATA_SEGMENT_LENGTH; conn->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->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(conn, 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->conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn->conn_target->t_alias != NULL) keys_add(response_keys, "TargetAlias", conn->conn_target->t_alias); keys_add_int(response_keys, "TargetPortalGroupTag", conn->conn_portal->p_portal_group->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->conn_session_type == CONN_SESSION_TYPE_NORMAL && conn->conn.conn_first_burst_length > conn->conn.conn_max_burst_length) { log_errx(1, "initiator sent FirstBurstLength > MaxBurstLength"); } conn->conn.conn_max_recv_data_segment_length = conn->conn_max_recv_data_segment_limit; keys_add_int(response_keys, "MaxRecvDataSegmentLength", conn->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); } static void login_wait_transition(struct ctld_connection *conn) { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; log_debugx("waiting for state transition request"); request = login_receive(&conn->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(conn, NULL); } void login(struct ctld_connection *conn) { 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->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->conn_portal->p_portal_group; memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid, sizeof(conn->conn_initiator_isid)); /* * XXX: Implement the C flag some day. */ request_keys = keys_new(); keys_load_pdu(request_keys, request); assert(conn->conn_initiator_name == NULL); 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->conn_initiator_name = checked_strdup(initiator_name); log_set_peer_name(conn->conn_initiator_name); setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name); redirected = login_portal_redirect(conn, request); if (redirected) { log_debugx("initiator redirected; exiting"); exit(0); } initiator_alias = keys_find(request_keys, "InitiatorAlias"); if (initiator_alias != NULL) conn->conn_initiator_alias = checked_strdup(initiator_alias); assert(conn->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->conn_session_type = CONN_SESSION_TYPE_NORMAL; } else if (strcmp(session_type, "Discovery") == 0) { conn->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->conn_session_type = CONN_SESSION_TYPE_NORMAL; assert(conn->conn_target == NULL); if (conn->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->conn_port = port_find_in_pg(pg, target_name); if (conn->conn_port == NULL) { login_send_error(request, 0x02, 0x03); log_errx(1, "requested target \"%s\" not found", target_name); } conn->conn_target = conn->conn_port->p_target; } /* * At this point we know what kind of authentication we need. */ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { ag = conn->conn_port->p_auth_group; if (ag == NULL) ag = conn->conn_target->t_auth_group; if (ag->ag_name != NULL) { log_debugx("initiator requests to connect " "to target \"%s\"; auth-group \"%s\"", conn->conn_target->t_name, ag->ag_name); } else { log_debugx("initiator requests to connect " "to target \"%s\"", conn->conn_target->t_name); } } else { assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); ag = pg->pg_discovery_auth_group; if (ag->ag_name != NULL) { log_debugx("initiator requests " "discovery session; auth-group \"%s\"", ag->ag_name); } else { log_debugx("initiator requests discovery session"); } } if (ag->ag_type == AG_TYPE_DENY) { login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type is \"deny\""); } if (ag->ag_type == AG_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 (!auth_name_check(ag, initiator_name)) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed initiator names"); } if (!auth_portal_check(ag, &conn->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->ag_type != AG_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(conn, 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->ag_type == AG_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->conn_session_type == CONN_SESSION_TYPE_NORMAL) { if (conn->conn_target->t_alias != NULL) keys_add(response_keys, "TargetAlias", conn->conn_target->t_alias); keys_add_int(response_keys, "TargetPortalGroupTag", pg->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->ag_type != AG_TYPE_NO_AUTHENTICATION) { login_chap(conn, ag); login_negotiate(conn, NULL); } else if (trans) { login_negotiate(conn, NULL); } else { login_wait_transition(conn); } } diff --git a/usr.sbin/ctld/uclparse.c b/usr.sbin/ctld/uclparse.cc similarity index 100% rename from usr.sbin/ctld/uclparse.c rename to usr.sbin/ctld/uclparse.cc