diff options
author | trasz <trasz@FreeBSD.org> | 2013-09-14 15:29:06 +0000 |
---|---|---|
committer | trasz <trasz@FreeBSD.org> | 2013-09-14 15:29:06 +0000 |
commit | a992abf0413f4cc428d4368e1d82d65f5b3d6397 (patch) | |
tree | d04af1389a0e20c7613b9dccaf4f3176084e40cc /usr.sbin/iscsid | |
parent | 889b9d0e0bc82fd4927dd02d64c973c36fa661a3 (diff) | |
download | FreeBSD-src-a992abf0413f4cc428d4368e1d82d65f5b3d6397.zip FreeBSD-src-a992abf0413f4cc428d4368e1d82d65f5b3d6397.tar.gz |
Bring in the new iSCSI target and initiator.
Reviewed by: ken (parts)
Approved by: re (delphij)
Sponsored by: FreeBSD Foundation
Diffstat (limited to 'usr.sbin/iscsid')
-rw-r--r-- | usr.sbin/iscsid/Makefile | 16 | ||||
-rw-r--r-- | usr.sbin/iscsid/discovery.c | 222 | ||||
-rw-r--r-- | usr.sbin/iscsid/iscsid.8 | 113 | ||||
-rw-r--r-- | usr.sbin/iscsid/iscsid.c | 576 | ||||
-rw-r--r-- | usr.sbin/iscsid/iscsid.h | 120 | ||||
-rw-r--r-- | usr.sbin/iscsid/keys.c | 217 | ||||
-rw-r--r-- | usr.sbin/iscsid/log.c | 196 | ||||
-rw-r--r-- | usr.sbin/iscsid/login.c | 868 | ||||
-rw-r--r-- | usr.sbin/iscsid/pdu.c | 281 |
9 files changed, 2609 insertions, 0 deletions
diff --git a/usr.sbin/iscsid/Makefile b/usr.sbin/iscsid/Makefile new file mode 100644 index 0000000..468b284 --- /dev/null +++ b/usr.sbin/iscsid/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +PROG= iscsid +SRCS= discovery.c iscsid.c keys.c log.c login.c pdu.c +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys/cam +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi +#CFLAGS+= -DICL_KERNEL_PROXY +MAN= iscsid.8 + +DPADD= ${LIBUTIL} +LDADD= -lcrypto -lssl -lutil + +WARNS= 6 + +.include <bsd.prog.mk> diff --git a/usr.sbin/iscsid/discovery.c b/usr.sbin/iscsid/discovery.c new file mode 100644 index 0000000..639b71a --- /dev/null +++ b/usr.sbin/iscsid/discovery.c @@ -0,0 +1,222 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> + +#include "iscsid.h" +#include "iscsi_proto.h" + +static struct pdu * +text_receive(struct connection *conn) +{ + struct pdu *response; + struct iscsi_bhs_text_response *bhstr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs; +#if 0 + if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0) + log_errx(1, "received Text PDU without the \"F\" flag"); +#endif + /* + * XXX: Implement the C flag some day. + */ + if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) + log_errx(1, "received Text PDU with unsupported \"C\" flag"); + if (response->pdu_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhstr->bhstr_statsn); + + return (response); +} + +static struct pdu * +text_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = pdu_new(conn); + bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; + bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhstr->bhstr_flags = BHSTR_FLAGS_FINAL; + bhstr->bhstr_initiator_task_tag = 0; + bhstr->bhstr_target_transfer_tag = 0xffffffff; + + bhstr->bhstr_initiator_task_tag = 0; /* XXX */ + bhstr->bhstr_cmdsn = 0; /* XXX */ + bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +static struct pdu * +logout_receive(struct connection *conn) +{ + struct pdu *response; + struct iscsi_bhs_logout_response *bhslr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGOUT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_response *)response->pdu_bhs; + if (ntohs(bhslr->bhslr_response) != BHSLR_RESPONSE_CLOSED_SUCCESSFULLY) + log_warnx("received Logout Response with reason %d", + ntohs(bhslr->bhslr_response)); + if (ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct pdu * +logout_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = pdu_new(conn); + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION; + bhslr->bhslr_reason |= 0x80; + bhslr->bhslr_initiator_task_tag = 0; /* XXX */ + bhslr->bhslr_cmdsn = 0; /* XXX */ + bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +static void +kernel_add(const struct connection *conn, const char *target) +{ + struct iscsi_session_add isa; + int error; + + memset(&isa, 0, sizeof(isa)); + memcpy(&isa.isa_conf, &conn->conn_conf, sizeof(isa)); + strlcpy(isa.isa_conf.isc_target, target, + sizeof(isa.isa_conf.isc_target)); + isa.isa_conf.isc_discovery = 0; + error = ioctl(conn->conn_iscsi_fd, ISCSISADD, &isa); + if (error != 0) + log_warn("failed to add %s: ISCSISADD", target); +} + +static void +kernel_remove(const struct connection *conn) +{ + struct iscsi_session_remove isr; + int error; + + memset(&isr, 0, sizeof(isr)); + isr.isr_session_id = conn->conn_session_id; + error = ioctl(conn->conn_iscsi_fd, ISCSISREMOVE, &isr); + if (error != 0) + log_warn("ISCSISREMOVE"); +} + +void +discovery(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + int i; + + log_debugx("beginning discovery session"); + request = text_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "SendTargets", "All"); + keys_save(request_keys, request); + keys_delete(request_keys); + request_keys = NULL; + pdu_send(request); + pdu_delete(request); + request = NULL; + + log_debugx("waiting for Text Response"); + response = text_receive(conn); + response_keys = keys_new(); + keys_load(response_keys, response); + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + if (strcmp(response_keys->keys_names[i], "TargetName") != 0) + continue; + + log_debugx("adding target %s", response_keys->keys_values[i]); + /* + * XXX: Validate the target name? + */ + kernel_add(conn, response_keys->keys_values[i]); + } + keys_delete(response_keys); + pdu_delete(response); + + log_debugx("removing temporary discovery session"); + kernel_remove(conn); + + log_debugx("discovery done; logging out"); + request = logout_new_request(conn); + pdu_send(request); + request = NULL; + + log_debugx("waiting for Logout Response"); + response = logout_receive(conn); + pdu_delete(response); + + log_debugx("discovery session done"); +} diff --git a/usr.sbin/iscsid/iscsid.8 b/usr.sbin/iscsid/iscsid.8 new file mode 100644 index 0000000..3adefdd --- /dev/null +++ b/usr.sbin/iscsid/iscsid.8 @@ -0,0 +1,113 @@ +.\" Copyright (c) 2012 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" 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 AUTHORS 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 AUTHORS 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 20, 2012 +.Dt ISCSID 8 +.Os +.Sh NAME +.Nm iscsid +.Nd iSCSI initiator daemon +.Sh SYNOPSIS +.Nm +.Op Fl P Ar pidfile +.Op Fl d +.Op Fl l Ar loglevel +.Op Fl m Ar maxproc +.Op Fl t Ar seconds +.Sh DESCRIPTION +The +.Nm +daemon is responsible for performing the Login Phase of iSCSI connections, +as well as performing SendTargets discovery. +.Pp +.Pp +Upon startup, the +.Nm +daemon opens the iSCSI initiator device file and waits for kernel requests. +It doesn't use any configuration file; all the information it needs it gets +from the kernel. +.Pp +When the +.Nm +damon is not running, already established iSCSI connections continue +to work. +However, establishing new connections, or recovering existing ones in case +of connection error, is not possible. +.Pp +The following options are available: +.Bl -tag -width ".Fl P Ar pidfile" +.It Fl P Ar pidfile +Specify alternative location of a file where main process PID will be stored. +The default location is /var/run/iscsid.pid. +.It Fl d +Debug mode. +The server sends verbose debug output to standard error, and does not +put itself in the background. +The server will also not fork and will exit after processing one connection. +This option is only intended for debugging the initiator. +.It Fl l Ar loglevel +Specifies debug level. +The default is 0. +.It Fl m Ar maxproc +Specifies limit for concurrently running child processes handling +connections. +The default is 30. +Setting it to 0 disables the limit. +.It Fl t Ar seconds +Specifies timeout for login session, after which the connection +will be forcibly terminated. +The default is 60. +Setting it to 0 disables the timeout. +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/iscsid.pid" -compact +.It Pa /dev/iscsi +The iSCSI initiator device file. +.It Pa /var/run/iscsid.pid +The default location of the +.Nm +PID file. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr iscsictl 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 10.0 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff --git a/usr.sbin/iscsid/iscsid.c b/usr.sbin/iscsid/iscsid.c new file mode 100644 index 0000000..7943eb1 --- /dev/null +++ b/usr.sbin/iscsid/iscsid.c @@ -0,0 +1,576 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/socket.h> +#include <sys/capability.h> +#include <sys/wait.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libutil.h> + +#include "iscsid.h" + +static volatile bool sigalrm_received = false; + +static int nchildren = 0; + +static void +usage(void) +{ + + fprintf(stderr, "usage: iscsid [-P pidfile][-d][-m maxproc][-t timeout]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +static int +resolve_addr(const char *address, struct addrinfo **ai) +{ + struct addrinfo hints; + char *arg, *addr, *ch; + const char *port; + int error, colons = 0; + + arg = checked_strdup(address); + + if (arg[0] == '\0') { + log_warnx("empty address"); + return (1); + } + if (arg[0] == '[') { + /* + * IPv6 address in square brackets, perhaps with port. + */ + arg++; + addr = strsep(&arg, "]"); + if (arg == NULL) { + log_warnx("invalid address %s", address); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid address %s", address); + 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 = "3260"; + } else { + addr = strsep(&arg, ":"); + if (arg == NULL) + port = "3260"; + 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); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + address, gai_strerror(error)); + return (1); + } + + return (0); +} + +static struct connection * +connection_new(unsigned int session_id, const struct iscsi_session_conf *conf, + int iscsi_fd) +{ + struct connection *conn; + struct addrinfo *from_ai, *to_ai; + const char *from_addr, *to_addr; +#ifdef ICL_KERNEL_PROXY + struct iscsi_daemon_connect *idc; +#endif + int error; + + conn = calloc(1, sizeof(*conn)); + if (conn == NULL) + log_err(1, "calloc"); + + /* + * Default values, from RFC 3720, section 12. + */ + conn->conn_header_digest = CONN_DIGEST_NONE; + conn->conn_data_digest = CONN_DIGEST_NONE; + conn->conn_initial_r2t = true; + conn->conn_immediate_data = true; + conn->conn_max_data_segment_length = 8192; + conn->conn_max_burst_length = 262144; + conn->conn_first_burst_length = 65536; + + conn->conn_session_id = session_id; + /* + * XXX: Should we sanitize this somehow? + */ + memcpy(&conn->conn_conf, conf, sizeof(conn->conn_conf)); + + from_addr = conn->conn_conf.isc_initiator_addr; + to_addr = conn->conn_conf.isc_target_addr; + + if (from_addr[0] != '\0') { + error = resolve_addr(from_addr, &from_ai); + if (error != 0) + log_errx(1, "failed to resolve initiator address %s", + from_addr); + } else { + from_ai = NULL; + } + + error = resolve_addr(to_addr, &to_ai); + if (error != 0) + log_errx(1, "failed to resolve target address %s", to_addr); + + conn->conn_iscsi_fd = iscsi_fd; + +#ifdef ICL_KERNEL_PROXY + + idc = calloc(1, sizeof(*idc)); + if (idc == NULL) + log_err(1, "calloc"); + + idc->idc_session_id = conn->conn_session_id; + if (conn->conn_conf.isc_iser) + idc->idc_iser = 1; + idc->idc_domain = to_ai->ai_family; + idc->idc_socktype = to_ai->ai_socktype; + idc->idc_protocol = to_ai->ai_protocol; + if (from_ai != NULL) { + idc->idc_from_addr = from_ai->ai_addr; + idc->idc_from_addrlen = from_ai->ai_addrlen; + } + idc->idc_to_addr = to_ai->ai_addr; + idc->idc_to_addrlen = to_ai->ai_addrlen; + + log_debugx("connecting to %s using ICL kernel proxy", to_addr); + error = ioctl(iscsi_fd, ISCSIDCONNECT, idc); + if (error != 0) { + fail(conn, strerror(errno)); + log_err(1, "failed to connect to %s using ICL kernel proxy", + to_addr); + } + +#else /* !ICL_KERNEL_PROXY */ + + if (conn->conn_conf.isc_iser) + log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY " + "does not support iSER"); + + conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype, + to_ai->ai_protocol); + if (conn->conn_socket < 0) + log_err(1, "failed to create socket for %s", from_addr); + if (from_ai != NULL) { + error = bind(conn->conn_socket, from_ai->ai_addr, + from_ai->ai_addrlen); + if (error != 0) + log_err(1, "failed to bind to %s", from_addr); + } + log_debugx("connecting to %s", to_addr); + error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen); + if (error != 0) { + fail(conn, strerror(errno)); + log_err(1, "failed to connect to %s", to_addr); + } + +#endif /* !ICL_KERNEL_PROXY */ + + return (conn); +} + +static void +handoff(struct connection *conn) +{ + struct iscsi_daemon_handoff *idh; + int error; + + log_debugx("handing off connection to the kernel"); + + idh = calloc(1, sizeof(*idh)); + if (idh == NULL) + log_err(1, "calloc"); + idh->idh_session_id = conn->conn_session_id; +#ifndef ICL_KERNEL_PROXY + idh->idh_socket = conn->conn_socket; +#endif + strlcpy(idh->idh_target_alias, conn->conn_target_alias, + sizeof(idh->idh_target_alias)); + memcpy(idh->idh_isid, conn->conn_isid, sizeof(idh->idh_isid)); + idh->idh_statsn = conn->conn_statsn; + idh->idh_header_digest = conn->conn_header_digest; + idh->idh_data_digest = conn->conn_data_digest; + idh->idh_initial_r2t = conn->conn_initial_r2t; + idh->idh_immediate_data = conn->conn_immediate_data; + idh->idh_max_data_segment_length = conn->conn_max_data_segment_length; + idh->idh_max_burst_length = conn->conn_max_burst_length; + idh->idh_first_burst_length = conn->conn_first_burst_length; + + error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, idh); + if (error != 0) + log_err(1, "ISCSIDHANDOFF"); +} + +void +fail(const struct connection *conn, const char *reason) +{ + struct iscsi_daemon_fail *idf; + int error; + + idf = calloc(1, sizeof(*idf)); + if (idf == NULL) + log_err(1, "calloc"); + + idf->idf_session_id = conn->conn_session_id; + strlcpy(idf->idf_reason, reason, sizeof(idf->idf_reason)); + + error = ioctl(conn->conn_iscsi_fd, ISCSIDFAIL, idf); + if (error != 0) + log_err(1, "ISCSIDFAIL"); +} + +/* + * XXX: I CANT INTO LATIN + */ +static void +capsicate(struct connection *conn) +{ + int error; + cap_rights_t rights; +#ifdef ICL_KERNEL_PROXY + const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE, + ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE }; +#else + const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, + ISCSISREMOVE }; +#endif + + cap_rights_init(&rights, CAP_IOCTL); + error = cap_rights_limit(conn->conn_iscsi_fd, &rights); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_rights_limit"); + + error = cap_ioctls_limit(conn->conn_iscsi_fd, cmds, + sizeof(cmds) / sizeof(cmds[0])); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_ioctls_limit"); + + error = cap_enter(); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_enter"); + + if (cap_sandboxed()) + log_debugx("Capsicum capability mode enabled"); + else + log_warnx("Capsicum capability mode not supported"); +} + +bool +timed_out(void) +{ + + return (sigalrm_received); +} + +static void +sigalrm_handler(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 +set_timeout(int timeout) +{ + struct sigaction sa; + struct itimerval itv; + int error; + + if (timeout <= 0) { + log_debugx("session timeout disabled"); + return; + } + + bzero(&sa, sizeof(sa)); + 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. + */ + bzero(&itv, sizeof(itv)); + itv.it_interval.tv_sec = 1; + itv.it_value.tv_sec = timeout; + + log_debugx("setting session timeout to %d seconds", + timeout); + error = setitimer(ITIMER_REAL, &itv, NULL); + if (error != 0) + log_err(1, "setitimer"); +} + +static void +handle_request(int iscsi_fd, struct iscsi_daemon_request *request, int timeout) +{ + struct connection *conn; + + log_set_peer_addr(request->idr_conf.isc_target_addr); + if (request->idr_conf.isc_target[0] != '\0') { + log_set_peer_name(request->idr_conf.isc_target); + setproctitle("%s (%s)", request->idr_conf.isc_target_addr, request->idr_conf.isc_target); + } else { + setproctitle("%s", request->idr_conf.isc_target_addr); + } + + conn = connection_new(request->idr_session_id, &request->idr_conf, iscsi_fd); + set_timeout(timeout); + capsicate(conn); + login(conn); + if (conn->conn_conf.isc_discovery != 0) + discovery(conn); + else + handoff(conn); + + log_debugx("nothing more to do; exiting"); + exit (0); +} + +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); +} + +int +main(int argc, char **argv) +{ + int ch, debug = 0, error, iscsi_fd, maxproc = 30, retval, saved_errno, + timeout = 60; + bool dont_daemonize = false; + struct pidfh *pidfh; + pid_t pid, otherpid; + const char *pidfile_path = DEFAULT_PIDFILE; + struct iscsi_daemon_request *request; + + while ((ch = getopt(argc, argv, "P:dl:m:t:")) != -1) { + switch (ch) { + case 'P': + pidfile_path = optarg; + break; + case 'd': + dont_daemonize = true; + debug++; + break; + case 'l': + debug = atoi(optarg); + break; + case 'm': + maxproc = atoi(optarg); + break; + case 't': + timeout = atoi(optarg); + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + log_init(debug); + + pidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (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\"", + pidfile_path); + } + + iscsi_fd = open(ISCSI_PATH, O_RDWR); + if (iscsi_fd < 0) { + saved_errno = errno; + retval = kldload("iscsi"); + if (retval != -1) + iscsi_fd = open(ISCSI_PATH, O_RDWR); + else + errno = saved_errno; + } + if (iscsi_fd < 0) + log_err(1, "failed to open %s", ISCSI_PATH); + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(pidfh); + exit(1); + } + } + + pidfile_write(pidfh); + + for (;;) { + log_debugx("waiting for request from the kernel"); + + request = calloc(1, sizeof(*request)); + if (request == NULL) + log_err(1, "calloc"); + + error = ioctl(iscsi_fd, ISCSIDWAIT, request); + if (error != 0) { + if (errno == EINTR) { + nchildren -= wait_for_children(false); + assert(nchildren >= 0); + continue; + } + + log_err(1, "ISCSIDWAIT"); + } + + if (dont_daemonize) { + log_debugx("not forking due to -d flag; " + "will exit after servicing a single request"); + } else { + nchildren -= wait_for_children(false); + assert(nchildren >= 0); + + while (maxproc > 0 && nchildren >= maxproc) { + log_debugx("maxproc limit of %d child processes hit; " + "waiting for child process to exit", 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) + continue; + } + + pidfile_close(pidfh); + handle_request(iscsi_fd, request, timeout); + } + + return (0); +} diff --git a/usr.sbin/iscsid/iscsid.h b/usr.sbin/iscsid/iscsid.h new file mode 100644 index 0000000..5af994a --- /dev/null +++ b/usr.sbin/iscsid/iscsid.h @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef ISCSID_H +#define ISCSID_H + +#include <stdbool.h> +#include <stdint.h> + +#include <iscsi_ioctl.h> + +#define DEFAULT_PIDFILE "/var/run/iscsid.pid" + +#define CONN_DIGEST_NONE 0 +#define CONN_DIGEST_CRC32C 1 + +#define CONN_MUTUAL_CHALLENGE_LEN 1024 + +struct connection { + int conn_iscsi_fd; +#ifndef ICL_KERNEL_PROXY + int conn_socket; +#endif + unsigned int conn_session_id; + struct iscsi_session_conf conn_conf; + char conn_target_alias[ISCSI_ADDR_LEN]; + uint8_t conn_isid[6]; + uint32_t conn_statsn; + int conn_header_digest; + int conn_data_digest; + bool conn_initial_r2t; + bool conn_immediate_data; + size_t conn_max_data_segment_length; + size_t conn_max_burst_length; + size_t conn_first_burst_length; + char conn_mutual_challenge[CONN_MUTUAL_CHALLENGE_LEN]; + unsigned char conn_mutual_id; +}; + +struct pdu { + struct connection *pdu_connection; + struct iscsi_bhs *pdu_bhs; + char *pdu_data; + size_t pdu_data_len; +}; + +#define KEYS_MAX 1024 + +struct keys { + char *keys_names[KEYS_MAX]; + char *keys_values[KEYS_MAX]; + char *keys_data; + size_t keys_data_len; +}; + +struct keys *keys_new(void); +void keys_delete(struct keys *key); +void keys_load(struct keys *keys, const struct pdu *pdu); +void keys_save(struct keys *keys, struct pdu *pdu); +const char *keys_find(struct keys *keys, const char *name); +int keys_find_int(struct keys *keys, const char *name); +void keys_add(struct keys *keys, + const char *name, const char *value); +void keys_add_int(struct keys *keys, + const char *name, int value); + +struct pdu *pdu_new(struct connection *ic); +struct pdu *pdu_new_response(struct pdu *request); +void pdu_receive(struct pdu *request); +void pdu_send(struct pdu *response); +void pdu_delete(struct pdu *ip); + +void login(struct connection *ic); + +void discovery(struct connection *ic); + +void log_init(int level); +void log_set_peer_name(const char *name); +void log_set_peer_addr(const char *addr); +void log_err(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_errx(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_warn(const char *, ...) __printf0like(1, 2); +void log_warnx(const char *, ...) __printflike(1, 2); +void log_debugx(const char *, ...) __printf0like(1, 2); + +char *checked_strdup(const char *); +bool timed_out(void); +void fail(const struct connection *, const char *); + +#endif /* !ISCSID_H */ diff --git a/usr.sbin/iscsid/keys.c b/usr.sbin/iscsid/keys.c new file mode 100644 index 0000000..d9055a2 --- /dev/null +++ b/usr.sbin/iscsid/keys.c @@ -0,0 +1,217 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "iscsid.h" + +struct keys * +keys_new(void) +{ + struct keys *keys; + + keys = calloc(sizeof(*keys), 1); + if (keys == NULL) + log_err(1, "calloc"); + + return (keys); +} + +void +keys_delete(struct keys *keys) +{ + + free(keys->keys_data); + free(keys); +} + +void +keys_load(struct keys *keys, const struct pdu *pdu) +{ + int i; + char *pair; + size_t pair_len; + + if (pdu->pdu_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(keys->keys_data == NULL); + keys->keys_data_len = pdu->pdu_data_len; + keys->keys_data = malloc(keys->keys_data_len); + if (keys->keys_data == NULL) + log_err(1, "malloc"); + memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len); + + /* + * XXX: Review this carefully. + */ + pair = keys->keys_data; + for (i = 0;; i++) { + if (i >= KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + keys->keys_values[i] = pair; + keys->keys_names[i] = strsep(&keys->keys_values[i], "="); + if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + keys->keys_names[i], keys->keys_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == keys->keys_data + keys->keys_data_len) + break; + assert(pair < keys->keys_data + keys->keys_data_len); + } +} + +void +keys_save(struct keys *keys, struct pdu *pdu) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(keys->keys_names[i]) + + strlen(keys->keys_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + pdu->pdu_data = data; + pdu->pdu_data_len = len; + + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + data += sprintf(data, "%s=%s", + keys->keys_names[i], keys->keys_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +keys_find(struct keys *keys, const char *name) +{ + int i; + + /* + * Note that we don't handle duplicated key names here, + * as they are not supposed to happen in requests, and if they do, + * it's an initiator error. + */ + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + return (NULL); + if (strcmp(keys->keys_names[i], name) == 0) + return (keys->keys_values[i]); + } + return (NULL); +} + +int +keys_find_int(struct keys *keys, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = keys_find(keys, name); + if (str == NULL) + return (-1); + + num = strtoul(str, &endptr, 10); + if (*endptr != '\0') { + log_debugx("invalid numeric value \"%s\"", str); + return (-1); + } + + return (num); +} + +void +keys_add(struct keys *keys, const char *name, const char *value) +{ + int i; + + log_debugx("key to send: \"%s=%s\"", name, value); + + /* + * Note that we don't check for duplicates here, as they are perfectly + * fine in responses, e.g. the "TargetName" keys in discovery sesion + * response. + */ + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) { + keys->keys_names[i] = checked_strdup(name); + keys->keys_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +keys_add_int(struct keys *keys, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + keys_add(keys, name, str); + free(str); +} diff --git a/usr.sbin/iscsid/log.c b/usr.sbin/iscsid/log.c new file mode 100644 index 0000000..be3aaed --- /dev/null +++ b/usr.sbin/iscsid/log.c @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <vis.h> + +#include "iscsid.h" + +static int log_level = 0; +static char *peer_name = NULL; +static char *peer_addr = NULL; + +#define MSGBUF_LEN 1024 + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_set_peer_name(const char *name) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_name != NULL) + log_errx(1, "%s called twice", __func__); + if (peer_addr == NULL) + log_errx(1, "%s called before log_set_peer_addr", __func__); + + peer_name = checked_strdup(name); +} + +void +log_set_peer_addr(const char *addr) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_addr != NULL) + log_errx(1, "%s called twice", __func__); + + peer_addr = checked_strdup(addr); +} + +static void +log_common(int priority, int log_errno, const char *fmt, va_list ap) +{ + static char msgbuf[MSGBUF_LEN]; + static char msgbuf_strvised[MSGBUF_LEN * 4 + 1]; + int ret; + + ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + if (ret < 0) { + fprintf(stderr, "%s: snprintf failed", getprogname()); + syslog(LOG_CRIT, "snprintf failed"); + exit(1); + } + + ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL); + if (ret < 0) { + fprintf(stderr, "%s: strnvis failed", getprogname()); + syslog(LOG_CRIT, "strnvis failed"); + exit(1); + } + + if (log_errno == -1) { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s\n", getprogname(), + peer_addr, peer_name, msgbuf_strvised); + syslog(priority, "%s (%s): %s", + peer_addr, peer_name, msgbuf_strvised); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s\n", getprogname(), + peer_addr, msgbuf_strvised); + syslog(priority, "%s: %s", + peer_addr, msgbuf_strvised); + } else { + fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised); + syslog(priority, "%s", msgbuf_strvised); + } + + } else { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(), + peer_addr, peer_name, msgbuf_strvised, strerror(errno)); + syslog(priority, "%s (%s): %s: %s", + peer_addr, peer_name, msgbuf_strvised, strerror(errno)); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s: %s\n", getprogname(), + peer_addr, msgbuf_strvised, strerror(errno)); + syslog(priority, "%s: %s: %s", + peer_addr, msgbuf_strvised, strerror(errno)); + } else { + fprintf(stderr, "%s: %s: %s\n", getprogname(), + msgbuf_strvised, strerror(errno)); + syslog(priority, "%s: %s", + msgbuf_strvised, strerror(errno)); + } + } +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, errno, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, -1, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, errno, fmt, ap); + va_end(ap); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, -1, fmt, ap); + va_end(ap); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap; + + if (log_level == 0) + return; + + va_start(ap, fmt); + log_common(LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} diff --git a/usr.sbin/iscsid/login.c b/usr.sbin/iscsid/login.c new file mode 100644 index 0000000..2ea47cb --- /dev/null +++ b/usr.sbin/iscsid/login.c @@ -0,0 +1,868 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> +#include <openssl/err.h> +#include <openssl/md5.h> +#include <openssl/rand.h> + +#include "iscsid.h" +#include "iscsi_proto.h" + +static int +login_nsg(const struct pdu *response) +{ + struct iscsi_bhs_login_response *bhslr; + + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + + return (bhslr->bhslr_flags & 0x03); +} + +static void +login_set_nsg(struct pdu *request, int nsg) +{ + struct iscsi_bhs_login_request *bhslr; + + assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || + nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static void +login_set_csg(struct pdu *request, int csg) +{ + struct iscsi_bhs_login_request *bhslr; + + assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || + csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + csg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static const char * +login_target_error_str(int class, int detail) +{ + static char msg[128]; + + /* + * RFC 3270, 10.13.5. Status-Class and Status-Detail + */ + switch (class) { + case 0x01: + switch (detail) { + case 0x01: + return ("Target moved temporarily"); + case 0x02: + return ("Target moved permanently"); + default: + snprintf(msg, sizeof(msg), "unknown redirection; " + "Status-Class 0x%x, Status-Detail 0x%x", + class, detail); + return (msg); + } + case 0x02: + switch (detail) { + case 0x00: + return ("Initiator error"); + case 0x01: + return ("Authentication failure"); + case 0x02: + return ("Authorization failure"); + case 0x03: + return ("Not found"); + case 0x04: + return ("Target removed"); + case 0x05: + return ("Unsupported version"); + case 0x06: + return ("Too many connections"); + case 0x07: + return ("Missing parameter"); + case 0x08: + return ("Can't include in session"); + case 0x09: + return ("Session type not supported"); + case 0x0a: + return ("Session does not exist"); + case 0x0b: + return ("Invalid during login"); + default: + snprintf(msg, sizeof(msg), "unknown initiator error; " + "Status-Class 0x%x, Status-Detail 0x%x", + class, detail); + return (msg); + } + case 0x03: + switch (detail) { + case 0x00: + return ("Target error"); + case 0x01: + return ("Service unavailable"); + case 0x02: + return ("Out of resources"); + default: + snprintf(msg, sizeof(msg), "unknown target error; " + "Status-Class 0x%x, Status-Detail 0x%x", + class, detail); + return (msg); + } + default: + snprintf(msg, sizeof(msg), "unknown error; " + "Status-Class 0x%x, Status-Detail 0x%x", + class, detail); + return (msg); + } +} + +static struct pdu * +login_receive(struct connection *conn, bool initial) +{ + struct pdu *response; + struct iscsi_bhs_login_response *bhslr; + const char *errorstr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) + log_errx(1, "received Login PDU with unsupported \"C\" flag"); + if (bhslr->bhslr_version_max != 0x00) + log_errx(1, "received Login PDU with unsupported " + "Version-max 0x%x", bhslr->bhslr_version_max); + if (bhslr->bhslr_version_active != 0x00) + log_errx(1, "received Login PDU with unsupported " + "Version-active 0x%x", bhslr->bhslr_version_active); + if (bhslr->bhslr_status_class != 0) { + errorstr = login_target_error_str(bhslr->bhslr_status_class, + bhslr->bhslr_status_detail); + fail(conn, errorstr); + log_errx(1, "target returned error: %s", errorstr); + } +#if 0 + if (response->pdu_data_len == 0) + log_errx(1, "received Login PDU with empty data segment"); +#endif + if (initial == false && + ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { + /* + * It's a warning, not an error, to work around what seems + * to be bug in NetBSD iSCSI target. + */ + log_warnx("received Login PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct pdu * +login_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = pdu_new(conn); + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; + login_set_csg(request, BHSLR_STAGE_SECURITY_NEGOTIATION); + login_set_nsg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid)); + bhslr->bhslr_initiator_task_tag = 0; + bhslr->bhslr_cmdsn = 0; + bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +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 int +login_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = login_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +login_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +static void +login_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, sizeof(id)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +static void +login_negotiate_key(struct connection *conn, const char *name, + const char *value) +{ + int which, tmp; + + if (strcmp(name, "TargetAlias") == 0) { + strlcpy(conn->conn_target_alias, value, + sizeof(conn->conn_target_alias)); + } else if (strcmp(value, "Irrelevant") == 0) { + /* Ignore. */ + } else if (strcmp(name, "HeaderDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for header digest; we'll use it"); + conn->conn_header_digest = CONN_DIGEST_CRC32C; + break; + case 2: + log_debugx("target prefers not to do " + "header digest; we'll comply"); + break; + default: + log_warnx("target sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for data digest; we'll use it"); + conn->conn_data_digest = CONN_DIGEST_CRC32C; + break; + case 2: + log_debugx("target prefers not to do " + "data digest; we'll comply"); + break; + default: + log_warnx("target sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + /* Ignore. */ + } else if (strcmp(name, "InitialR2T") == 0) { + if (strcmp(value, "Yes") == 0) + conn->conn_initial_r2t = true; + else + conn->conn_initial_r2t = false; + } else if (strcmp(name, "ImmediateData") == 0) { + if (strcmp(value, "Yes") == 0) + conn->conn_immediate_data = true; + else + conn->conn_immediate_data = false; + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + conn->conn_max_data_segment_length = tmp; + } else if (strcmp(name, "MaxBurstLength") == 0) { + if (conn->conn_immediate_data) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid MaxBurstLength"); + conn->conn_max_burst_length = tmp; + } + } else if (strcmp(name, "FirstBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid FirstBurstLength"); + conn->conn_first_burst_length = tmp; + } else if (strcmp(name, "DefaultTime2Wait") == 0) { + /* Ignore */ + } else if (strcmp(name, "DefaultTime2Retain") == 0) { + /* Ignore */ + } else if (strcmp(name, "MaxOutstandingR2T") == 0) { + /* Ignore */ + } else if (strcmp(name, "DataPDUInOrder") == 0) { + /* Ignore */ + } else if (strcmp(name, "DataSequenceInOrder") == 0) { + /* Ignore */ + } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { + /* Ignore */ + } else if (strcmp(name, "OFMarker") == 0) { + /* Ignore */ + } else if (strcmp(name, "IFMarker") == 0) { + /* Ignore */ + } else if (strcmp(name, "TargetPortalGroupTag") == 0) { + /* Ignore */ + } else { + log_debugx("unknown key \"%s\"; ignoring", name); + } +} + +static void +login_negotiate(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr; + int i; + + log_debugx("beginning parameter negotiation"); + request = login_new_request(conn); + login_set_csg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + login_set_nsg(request, BHSLR_STAGE_FULL_FEATURE_PHASE); + request_keys = keys_new(); + if (conn->conn_conf.isc_discovery == 0) { + if (conn->conn_conf.isc_header_digest != 0) + keys_add(request_keys, "HeaderDigest", "CRC32C"); + if (conn->conn_conf.isc_data_digest != 0) + keys_add(request_keys, "DataDigest", "CRC32C"); + + keys_add(request_keys, "ImmediateData", "Yes"); + keys_add_int(request_keys, "MaxBurstLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + keys_add_int(request_keys, "FirstBurstLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + } + keys_add(request_keys, "InitialR2T", "Yes"); + keys_add_int(request_keys, "MaxRecvDataSegmentLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + keys_add(request_keys, "DefaultTime2Wait", "0"); + keys_add(request_keys, "DefaultTime2Retain", "0"); + keys_save(request_keys, request); + keys_delete(request_keys); + request_keys = NULL; + pdu_send(request); + pdu_delete(request); + request = NULL; + + response = login_receive(conn, false); + response_keys = keys_new(); + keys_load(response_keys, response); + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + login_negotiate_key(conn, + response_keys->keys_names[i], response_keys->keys_values[i]); + } + + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("received final login response " + "without the \"T\" flag"); + else if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) + log_warnx("received final login response with wrong NSG 0x%x", + login_nsg(response)); + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + keys_delete(response_keys); + pdu_delete(response); +} + +static void +login_send_chap_a(struct connection *conn) +{ + struct pdu *request; + struct keys *request_keys; + + request = login_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "CHAP_A", "5"); + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); +} + +static void +login_send_chap_r(struct pdu *response) +{ + struct connection *conn; + struct pdu *request; + struct keys *request_keys, *response_keys; + const char *chap_a, *chap_c, *chap_i; + char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; + size_t challenge_len; + int error, rv; + unsigned char id; + char *mutual_chap_c, mutual_chap_i[4]; + + /* + * As in the rest of the initiator, 'request' means + * 'initiator -> target', and 'response' means 'target -> initiator', + * + * So, here the 'response' from the target is the packet that contains + * CHAP challenge; our CHAP response goes into 'request'. + */ + + conn = response->pdu_connection; + + response_keys = keys_new(); + keys_load(response_keys, response); + + /* + * First, compute the response. + */ + chap_a = keys_find(response_keys, "CHAP_A"); + if (chap_a == NULL) + log_errx(1, "received CHAP packet without CHAP_A"); + chap_c = keys_find(response_keys, "CHAP_C"); + if (chap_c == NULL) + log_errx(1, "received CHAP packet without CHAP_C"); + chap_i = keys_find(response_keys, "CHAP_I"); + if (chap_i == NULL) + log_errx(1, "received CHAP packet without CHAP_I"); + + if (strcmp(chap_a, "5") != 0) + log_errx(1, "received CHAP packet " + "with unsupported CHAP_A \"%s\"", chap_a); + id = strtoul(chap_i, NULL, 10); + error = login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) + log_errx(1, "received CHAP packet with malformed CHAP_C"); + login_compute_md5(id, conn->conn_conf.isc_secret, + challenge, challenge_len, response_bin, sizeof(response_bin)); + free(challenge); + chap_r = login_bin2hex(response_bin, sizeof(response_bin)); + + keys_delete(response_keys); + + request = login_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user); + keys_add(request_keys, "CHAP_R", chap_r); + free(chap_r); + + /* + * If we want mutual authentication, we're expected to send + * our CHAP_I/CHAP_C now. + */ + if (conn->conn_conf.isc_mutual_user[0] != '\0') { + log_debugx("requesting mutual authentication; " + "binary challenge size is %zd bytes", + sizeof(conn->conn_mutual_challenge)); + + rv = RAND_bytes(conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&conn->conn_mutual_id, + sizeof(conn->conn_mutual_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge)); + snprintf(mutual_chap_i, sizeof(mutual_chap_i), + "%d", conn->conn_mutual_id); + keys_add(request_keys, "CHAP_I", mutual_chap_i); + keys_add(request_keys, "CHAP_C", mutual_chap_c); + free(mutual_chap_c); + } + + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); +} + +static void +login_verify_mutual(const struct pdu *response) +{ + struct connection *conn; + struct keys *response_keys; + const char *chap_n, *chap_r; + char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; + size_t response_bin_len; + int error; + + conn = response->pdu_connection; + + response_keys = keys_new(); + keys_load(response_keys, response); + + chap_n = keys_find(response_keys, "CHAP_N"); + if (chap_n == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_N"); + chap_r = keys_find(response_keys, "CHAP_R"); + if (chap_r == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_R"); + error = login_hex2bin(chap_r, &response_bin, &response_bin_len); + if (error != 0) + log_errx(1, "received CHAP Response PDU with malformed CHAP_R"); + + if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) { + fail(conn, "Mutual CHAP failed"); + log_errx(1, "mutual CHAP authentication failed: wrong user"); + } + + login_compute_md5(conn->conn_mutual_id, + conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge), expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) { + fail(conn, "Mutual CHAP failed"); + log_errx(1, "mutual CHAP authentication failed: wrong secret"); + } + + keys_delete(response_keys); + free(response_bin); + + log_debugx("mutual CHAP authentication succeeded"); +} + +static void +login_chap(struct connection *conn) +{ + struct pdu *response; + + log_debugx("beginning CHAP authentication; sending CHAP_A"); + login_send_chap_a(conn); + + log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); + response = login_receive(conn, false); + + log_debugx("sending CHAP_N/CHAP_R"); + login_send_chap_r(response); + pdu_delete(response); + + /* + * XXX: Make sure this is not susceptible to MITM. + */ + + log_debugx("waiting for CHAP result"); + response = login_receive(conn, false); + if (conn->conn_conf.isc_mutual_user[0] != '\0') + login_verify_mutual(response); + pdu_delete(response); + + log_debugx("CHAP authentication done"); +} + +static void +login_create_isid(struct connection *conn) +{ + int rv; + + /* + * RFC 3720, 10.12.5: 10b, "Random" ISID. + * + */ + conn->conn_isid[0] = 0x80; + + rv = RAND_bytes(&conn->conn_isid[1], 3); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +} + +void +login(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + const char *auth_method; + int i; + + login_create_isid(conn); + + log_debugx("beginning Login phase; sending Login PDU"); + request = login_new_request(conn); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + + request_keys = keys_new(); + if (conn->conn_conf.isc_user[0] == '\0') + keys_add(request_keys, "AuthMethod", "None"); + else + keys_add(request_keys, "AuthMethod", "CHAP,None"); + keys_add(request_keys, "InitiatorName", + conn->conn_conf.isc_initiator); + if (conn->conn_conf.isc_initiator_alias[0] != '\0') { + keys_add(request_keys, "InitiatorAlias", + conn->conn_conf.isc_initiator_alias); + } + if (conn->conn_conf.isc_discovery == 0) { + keys_add(request_keys, "SessionType", "Normal"); + keys_add(request_keys, + "TargetName", conn->conn_conf.isc_target); + } else { + keys_add(request_keys, "SessionType", "Discovery"); + } + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); + + response = login_receive(conn, true); + + response_keys = keys_new(); + keys_load(response_keys, response); + + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + /* + * Not interested in AuthMethod at this point; we only need + * to parse things such as TargetAlias. + * + * XXX: This is somewhat ugly. We should have a way to apply + * all the keys to the session and use that by default + * instead of discarding them. + */ + if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0) + continue; + + login_negotiate_key(conn, + response_keys->keys_names[i], response_keys->keys_values[i]); + } + + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && + login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + log_debugx("target requested transition " + "to operational negotiation"); + + keys_delete(response_keys); + pdu_delete(response); + login_negotiate(conn); + return; + } + + auth_method = keys_find(response_keys, "AuthMethod"); + if (auth_method == NULL) + log_errx(1, "received response without AuthMethod"); + if (strcmp(auth_method, "None") == 0) { + log_debugx("target does not require authentication"); + keys_delete(response_keys); + pdu_delete(response); + login_negotiate(conn); + return; + } + + if (strcmp(auth_method, "CHAP") != 0) { + fail(conn, "Unsupported AuthMethod"); + log_errx(1, "received response " + "with unsupported AuthMethod \"%s\"", auth_method); + } + + if (conn->conn_conf.isc_user[0] == '\0' || + conn->conn_conf.isc_secret[0] == '\0') { + fail(conn, "Authentication required"); + log_errx(1, "target requests CHAP authentication, but we don't " + "have user and secret"); + } + + keys_delete(response_keys); + response_keys = NULL; + pdu_delete(response); + response = NULL; + + login_chap(conn); + login_negotiate(conn); +} diff --git a/usr.sbin/iscsid/pdu.c b/usr.sbin/iscsid/pdu.c new file mode 100644 index 0000000..24b63d7 --- /dev/null +++ b/usr.sbin/iscsid/pdu.c @@ -0,0 +1,281 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "iscsid.h" +#include "iscsi_proto.h" + +#ifdef ICL_KERNEL_PROXY +#include <sys/ioctl.h> +#endif + +static int +pdu_ahs_length(const struct pdu *pdu) +{ + + return (pdu->pdu_bhs->bhs_total_ahs_len * 4); +} + +static int +pdu_data_segment_length(const struct pdu *pdu) +{ + uint32_t len = 0; + + len += pdu->pdu_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +pdu_set_data_segment_length(struct pdu *pdu, uint32_t len) +{ + + pdu->pdu_bhs->bhs_data_segment_len[2] = len; + pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8; + pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16; +} + +struct pdu * +pdu_new(struct connection *conn) +{ + struct pdu *pdu; + + pdu = calloc(sizeof(*pdu), 1); + if (pdu == NULL) + log_err(1, "calloc"); + + pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1); + if (pdu->pdu_bhs == NULL) + log_err(1, "calloc"); + + pdu->pdu_connection = conn; + + return (pdu); +} + +struct pdu * +pdu_new_response(struct pdu *request) +{ + + return (pdu_new(request->pdu_connection)); +} + +#ifdef ICL_KERNEL_PROXY + +void +pdu_receive(struct pdu *pdu) +{ + struct iscsi_daemon_receive *idr; + size_t len; + int error; + + pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + idr = calloc(1, sizeof(*idr)); + if (idr == NULL) + log_err(1, "calloc"); + + idr->idr_session_id = pdu->pdu_connection->conn_session_id; + idr->idr_bhs = pdu->pdu_bhs; + idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH; + idr->idr_data_segment = pdu->pdu_data; + + error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr); + if (error != 0) + log_err(1, "ISCSIDRECEIVE"); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH); + pdu->pdu_data_len = len; + + free(idr); +} + +void +pdu_send(struct pdu *pdu) +{ + struct iscsi_daemon_send *ids; + int error; + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + + ids = calloc(1, sizeof(*ids)); + if (ids == NULL) + log_err(1, "calloc"); + + ids->ids_session_id = pdu->pdu_connection->conn_session_id; + ids->ids_bhs = pdu->pdu_bhs; + ids->ids_data_segment_len = pdu->pdu_data_len; + ids->ids_data_segment = pdu->pdu_data; + + error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids); + if (error != 0) + log_err(1, "ISCSIDSEND"); + + free(ids); +} + +#else /* !ICL_KERNEL_PROXY */ + +static size_t +pdu_padding(const struct pdu *pdu) +{ + + if ((pdu->pdu_data_len % 4) != 0) + return (4 - (pdu->pdu_data_len % 4)); + + return (0); +} + +static void +pdu_read(int fd, char *data, size_t len) +{ + ssize_t ret; + + while (len > 0) { + ret = read(fd, data, len); + if (ret < 0) { + if (timed_out()) + log_errx(1, "exiting due to timeout"); + log_err(1, "read"); + } else if (ret == 0) + log_errx(1, "read: connection lost"); + len -= ret; + data += ret; + } +} + +void +pdu_receive(struct pdu *pdu) +{ + size_t len, padding; + char dummy[4]; + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs)); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + if (len > 0) { + if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) { + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %d", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + } + + pdu->pdu_data_len = len; + pdu->pdu_data = malloc(len); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_data, pdu->pdu_data_len); + + padding = pdu_padding(pdu); + if (padding != 0) { + assert(padding < sizeof(dummy)); + pdu_read(pdu->pdu_connection->conn_socket, + (char *)dummy, padding); + } + } +} + +void +pdu_send(struct pdu *pdu) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + iov[0].iov_base = pdu->pdu_bhs; + iov[0].iov_len = sizeof(*pdu->pdu_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (pdu->pdu_data_len > 0) { + iov[1].iov_base = pdu->pdu_data; + iov[1].iov_len = pdu->pdu_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = pdu_padding(pdu); + if (padding > 0) { + assert(padding < sizeof(zero)); + iov[2].iov_base = &zero; + iov[2].iov_len = padding; + total_len += iov[2].iov_len; + iovcnt = 3; + } + } + + ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt); + if (ret < 0) { + if (timed_out()) + log_errx(1, "exiting due to timeout"); + log_err(1, "writev"); + } + if (ret != total_len) + log_errx(1, "short write"); +} + +#endif /* !ICL_KERNEL_PROXY */ + +void +pdu_delete(struct pdu *pdu) +{ + + free(pdu->pdu_data); + free(pdu->pdu_bhs); + free(pdu); +} |