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 | |
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')
-rw-r--r-- | usr.sbin/Makefile | 2 | ||||
-rw-r--r-- | usr.sbin/ctladm/ctladm.8 | 48 | ||||
-rw-r--r-- | usr.sbin/ctladm/ctladm.c | 421 | ||||
-rw-r--r-- | usr.sbin/ctld/Makefile | 21 | ||||
-rw-r--r-- | usr.sbin/ctld/ctl.conf.5 | 244 | ||||
-rw-r--r-- | usr.sbin/ctld/ctld.8 | 114 | ||||
-rw-r--r-- | usr.sbin/ctld/ctld.c | 1715 | ||||
-rw-r--r-- | usr.sbin/ctld/ctld.h | 277 | ||||
-rw-r--r-- | usr.sbin/ctld/discovery.c | 221 | ||||
-rw-r--r-- | usr.sbin/ctld/kernel.c | 782 | ||||
-rw-r--r-- | usr.sbin/ctld/keys.c | 216 | ||||
-rw-r--r-- | usr.sbin/ctld/log.c | 196 | ||||
-rw-r--r-- | usr.sbin/ctld/login.c | 1051 | ||||
-rw-r--r-- | usr.sbin/ctld/parse.y | 621 | ||||
-rw-r--r-- | usr.sbin/ctld/pdu.c | 246 | ||||
-rw-r--r-- | usr.sbin/ctld/token.l | 85 | ||||
-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 |
25 files changed, 8867 insertions, 2 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index f76988f..f2f5342 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -17,6 +17,7 @@ SUBDIR= adduser \ crashinfo \ cron \ ctladm \ + ctld \ daemon \ dconschat \ devinfo \ @@ -35,6 +36,7 @@ SUBDIR= adduser \ ifmcstat \ inetd \ iostat \ + iscsid \ isfctl \ kldxref \ mailwrapper \ diff --git a/usr.sbin/ctladm/ctladm.8 b/usr.sbin/ctladm/ctladm.8 index 68e79ff..b9d4e61 100644 --- a/usr.sbin/ctladm/ctladm.8 +++ b/usr.sbin/ctladm/ctladm.8 @@ -197,6 +197,16 @@ .Nm .Ic dumpstructs .Nm +.Ic islist +.Op Fl v +.Op Fl x +.Nm +.Ic islogout +.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name +.Nm +.Ic isterminate +.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name +.Nm .Ic help .Sh DESCRIPTION The @@ -883,6 +893,41 @@ If you specify .Fl x , the entire LUN database is displayed in XML format. .El +.It Ic islist +Get a list of currently running iSCSI connections. +This includes initiator and target names and the unique connection IDs. +.Bl -tag -width 11n +.It Fl v +Verbose mode. +.It Fl x +Dump the raw XML. +The connections list information from the kernel comes in XML format, and this +option allows the display of the raw XML data. +.El +.It Ic islogout +Ask the initiator to log out iSCSI connections matching criteria. +.Bl -tag -width 11n +.It Fl a +Log out all connections. +.It Fl h +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl i +Specify initiator name. +.El +.It Ic isterminate +Forcibly terminate iSCSI connections matching criteria. +.Bl -tag -width 11n +.It Fl a +Terminate all connections. +.It Fl h +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl i +Specify initiator name. +.El .It Ic help Display .Nm @@ -977,7 +1022,8 @@ This will result in a sense key of NOT READY (0x02), and an ASC/ASCQ of .Xr cam 4 , .Xr ctl 4 , .Xr xpt 4 , -.Xr camcontrol 8 +.Xr camcontrol 8 , +.Xr ctld 8 .Sh HISTORY The .Nm diff --git a/usr.sbin/ctladm/ctladm.c b/usr.sbin/ctladm/ctladm.c index 17c1148..d26bf72 100644 --- a/usr.sbin/ctladm/ctladm.c +++ b/usr.sbin/ctladm/ctladm.c @@ -117,7 +117,10 @@ typedef enum { CTLADM_CMD_PRES_OUT, CTLADM_CMD_INQ_VPD_DEVID, CTLADM_CMD_RTPG, - CTLADM_CMD_MODIFY + CTLADM_CMD_MODIFY, + CTLADM_CMD_ISLIST, + CTLADM_CMD_ISLOGOUT, + CTLADM_CMD_ISTERMINATE } ctladm_cmdfunction; typedef enum { @@ -180,6 +183,9 @@ static struct ctladm_opts option_table[] = { {"help", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL}, {"inject", CTLADM_CMD_ERR_INJECT, CTLADM_ARG_NEED_TL, "cd:i:p:r:s:"}, {"inquiry", CTLADM_CMD_INQUIRY, CTLADM_ARG_NEED_TL, NULL}, + {"islist", CTLADM_CMD_ISLIST, CTLADM_ARG_NONE, "vx"}, + {"islogout", CTLADM_CMD_ISLOGOUT, CTLADM_ARG_NONE, "ah:c:i:"}, + {"isterminate", CTLADM_CMD_ISTERMINATE, CTLADM_ARG_NONE, "ah:c:i:"}, {"lunlist", CTLADM_CMD_LUNLIST, CTLADM_ARG_NONE, NULL}, {"modesense", CTLADM_CMD_MODESENSE, CTLADM_ARG_NEED_TL, "P:S:dlm:c:"}, {"modify", CTLADM_CMD_MODIFY, CTLADM_ARG_NONE, "b:l:s:"}, @@ -489,6 +495,9 @@ retry: case CTL_PORT_ISC: type = "ISC"; break; + case CTL_PORT_ISCSI: + type = "ISCSI"; + break; default: type = "UNKNOWN"; break; @@ -578,6 +587,7 @@ static struct ctladm_opts cctl_fe_table[] = { {"fc", CTL_PORT_FC, CTLADM_ARG_NONE, NULL}, {"scsi", CTL_PORT_SCSI, CTLADM_ARG_NONE, NULL}, {"internal", CTL_PORT_INTERNAL, CTLADM_ARG_NONE, NULL}, + {"iscsi", CTL_PORT_ISCSI, CTLADM_ARG_NONE, NULL}, {"all", CTL_PORT_ALL, CTLADM_ARG_NONE, NULL}, {NULL, 0, 0, NULL} }; @@ -3399,6 +3409,403 @@ bailout: return (retval); } +struct cctl_islist_conn { + int connection_id; + char *initiator; + char *initiator_addr; + char *initiator_alias; + char *target; + char *target_alias; + char *header_digest; + char *data_digest; + char *max_data_segment_length;; + int immediate_data; + int iser; + STAILQ_ENTRY(cctl_islist_conn) links; +}; + +struct cctl_islist_data { + int num_conns; + STAILQ_HEAD(,cctl_islist_conn) conn_list; + struct cctl_islist_conn *cur_conn; + int level; + struct sbuf *cur_sb[32]; +}; + +static void +cctl_islist_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + islist->level++; + if ((u_int)islist->level > (sizeof(islist->cur_sb) / + sizeof(islist->cur_sb[0]))) + errx(1, "%s: too many nesting levels, %zd max", __func__, + sizeof(islist->cur_sb) / sizeof(islist->cur_sb[0])); + + islist->cur_sb[islist->level] = sbuf_new_auto(); + if (islist->cur_sb[islist->level] == NULL) + err(1, "%s: Unable to allocate sbuf", __func__); + + if (strcmp(name, "connection") == 0) { + if (cur_conn != NULL) + errx(1, "%s: improper connection element nesting", + __func__); + + cur_conn = calloc(1, sizeof(*cur_conn)); + if (cur_conn == NULL) + err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_conn)); + + islist->num_conns++; + islist->cur_conn = cur_conn; + + STAILQ_INSERT_TAIL(&islist->conn_list, cur_conn, links); + + for (i = 0; attr[i] != NULL; i += 2) { + if (strcmp(attr[i], "id") == 0) { + cur_conn->connection_id = + strtoull(attr[i+1], NULL, 0); + } else { + errx(1, + "%s: invalid connection attribute %s = %s", + __func__, attr[i], attr[i+1]); + } + } + } +} + +static void +cctl_islist_end_element(void *user_data, const char *name) +{ + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + char *str; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + + if ((cur_conn == NULL) + && (strcmp(name, "ctlislist") != 0)) + errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name); + + if (islist->cur_sb[islist->level] == NULL) + errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, + islist->level, name); + + sbuf_finish(islist->cur_sb[islist->level]); + str = strdup(sbuf_data(islist->cur_sb[islist->level])); + if (str == NULL) + err(1, "%s can't allocate %zd bytes for string", __func__, + sbuf_len(islist->cur_sb[islist->level])); + + sbuf_delete(islist->cur_sb[islist->level]); + islist->cur_sb[islist->level] = NULL; + islist->level--; + + if (strcmp(name, "initiator") == 0) { + cur_conn->initiator = str; + str = NULL; + } else if (strcmp(name, "initiator_addr") == 0) { + cur_conn->initiator_addr = str; + str = NULL; + } else if (strcmp(name, "initiator_alias") == 0) { + cur_conn->initiator_alias = str; + str = NULL; + } else if (strcmp(name, "target") == 0) { + cur_conn->target = str; + str = NULL; + } else if (strcmp(name, "target_alias") == 0) { + cur_conn->target_alias = str; + str = NULL; + } else if (strcmp(name, "header_digest") == 0) { + cur_conn->header_digest = str; + str = NULL; + } else if (strcmp(name, "data_digest") == 0) { + cur_conn->data_digest = str; + str = NULL; + } else if (strcmp(name, "max_data_segment_length") == 0) { + cur_conn->max_data_segment_length = str; + str = NULL; + } else if (strcmp(name, "immediate_data") == 0) { + cur_conn->immediate_data = atoi(str); + } else if (strcmp(name, "iser") == 0) { + cur_conn->iser = atoi(str); + } else if (strcmp(name, "connection") == 0) { + islist->cur_conn = NULL; + } else if (strcmp(name, "ctlislist") == 0) { + } else + errx(1, "unknown element %s", name); + + free(str); +} + +static void +cctl_islist_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct cctl_islist_data *islist; + + islist = (struct cctl_islist_data *)user_data; + + sbuf_bcat(islist->cur_sb[islist->level], str, len); +} + +static int +cctl_islist(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + struct cctl_islist_data islist; + struct cctl_islist_conn *conn; + XML_Parser parser; + char *conn_str; + int conn_len; + int dump_xml = 0; + int c, retval, verbose = 0; + + retval = 0; + conn_len = 4096; + + bzero(&islist, sizeof(islist)); + STAILQ_INIT(&islist.conn_list); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'v': + verbose = 1; + break; + case 'x': + dump_xml = 1; + break; + default: + break; + } + } + +retry: + conn_str = malloc(conn_len); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_LIST; + req.data.list.alloc_len = conn_len; + req.data.list.conn_xml = conn_str; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status == CTL_ISCSI_ERROR) { + warnx("%s: error returned from CTL_ISCSI ioctl:\n%s", + __func__, req.error_str); + } else if (req.status == CTL_ISCSI_LIST_NEED_MORE_SPACE) { + conn_len = conn_len << 1; + goto retry; + } + + if (dump_xml != 0) { + printf("%s", conn_str); + goto bailout; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + warn("%s: Unable to create XML parser", __func__); + retval = 1; + goto bailout; + } + + XML_SetUserData(parser, &islist); + XML_SetElementHandler(parser, cctl_islist_start_element, + cctl_islist_end_element); + XML_SetCharacterDataHandler(parser, cctl_islist_char_handler); + + retval = XML_Parse(parser, conn_str, strlen(conn_str), 1); + XML_ParserFree(parser); + if (retval != 1) { + retval = 1; + goto bailout; + } + + if (verbose != 0) { + STAILQ_FOREACH(conn, &islist.conn_list, links) { + printf("Session ID: %d\n", conn->connection_id); + printf("Initiator name: %s\n", conn->initiator); + printf("Initiator addr: %s\n", conn->initiator_addr); + printf("Initiator alias: %s\n", conn->initiator_alias); + printf("Target name: %s\n", conn->target); + printf("Target alias: %s\n", conn->target_alias); + printf("Header digest: %s\n", conn->header_digest); + printf("Data digest: %s\n", conn->data_digest); + printf("DataSegmentLen: %s\n", conn->max_data_segment_length); + printf("ImmediateData: %s\n", conn->immediate_data ? "Yes" : "No"); + printf("iSER (RDMA): %s\n", conn->iser ? "Yes" : "No"); + printf("\n"); + } + } else { + printf("%4s %-16s %-36s %-36s\n", "ID", "Address", "Initiator name", + "Target name"); + STAILQ_FOREACH(conn, &islist.conn_list, links) { + printf("%4u %-16s %-36s %-36s\n", + conn->connection_id, conn->initiator_addr, conn->initiator, + conn->target); + } + } +bailout: + free(conn_str); + + return (retval); +} + +static int +cctl_islogout(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + int retval = 0, c; + int all = 0, connection_id = -1, nargs = 0; + char *initiator_name = NULL, *initiator_addr = NULL; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'a': + all = 1; + nargs++; + break; + case 'h': + initiator_addr = strdup(optarg); + if (initiator_addr == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + case 'c': + connection_id = strtoul(optarg, NULL, 0); + nargs++; + break; + case 'i': + initiator_name = strdup(optarg); + if (initiator_name == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + default: + break; + } + } + + if (nargs == 0) + errx(1, "%s: either -a, -h, -c, or -i must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -a, -h, -c, or -i may be specified", + __func__); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_LOGOUT; + req.data.logout.connection_id = connection_id; + if (initiator_addr != NULL) + strlcpy(req.data.logout.initiator_addr, + initiator_addr, sizeof(req.data.logout.initiator_addr)); + if (initiator_name != NULL) + strlcpy(req.data.logout.initiator_name, + initiator_name, sizeof(req.data.logout.initiator_name)); + if (all != 0) + req.data.logout.all = 1; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status != CTL_ISCSI_OK) { + warnx("%s: error returned from CTL iSCSI logout request:\n%s", + __func__, req.error_str); + retval = 1; + goto bailout; + } + + printf("iSCSI logout requests submitted\n"); + +bailout: + return (retval); +} + +static int +cctl_isterminate(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + int retval = 0, c; + int all = 0, connection_id = -1, nargs = 0; + char *initiator_name = NULL, *initiator_addr = NULL; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'a': + all = 1; + nargs++; + break; + case 'h': + initiator_addr = strdup(optarg); + if (initiator_addr == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + case 'c': + connection_id = strtoul(optarg, NULL, 0); + nargs++; + break; + case 'i': + initiator_name = strdup(optarg); + if (initiator_name == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + default: + break; + } + } + + if (nargs == 0) + errx(1, "%s: either -a, -h, -c, or -i must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -a, -h, -c, or -i may be specified", + __func__); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_TERMINATE; + req.data.terminate.connection_id = connection_id; + if (initiator_addr != NULL) + strlcpy(req.data.terminate.initiator_addr, + initiator_addr, sizeof(req.data.terminate.initiator_addr)); + if (initiator_name != NULL) + strlcpy(req.data.terminate.initiator_name, + initiator_name, sizeof(req.data.terminate.initiator_name)); + if (all != 0) + req.data.terminate.all = 1; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status != CTL_ISCSI_OK) { + warnx("%s: error returned from CTL iSCSI connection " + "termination request:\n%s", __func__, req.error_str); + retval = 1; + goto bailout; + } + + printf("iSCSI connections terminated\n"); + +bailout: + return (retval); +} /* * Name/value pair used for per-LUN attributes. @@ -3713,6 +4120,9 @@ usage(int error) " [-s len fmt [args]] [-c] [-d delete_id]\n" " ctladm port <-l | -o <on|off> | [-w wwnn][-W wwpn]>\n" " [-p targ_port] [-t port_type] [-q] [-x]\n" +" ctladm islist [-v | -x]\n" +" ctladm islogout <-A | -a addr | -c connection-id | -n name>\n" +" ctladm isterminate <-A | -a addr | -c connection-id | -n name>\n" " ctladm dumpooa\n" " ctladm dumpstructs\n" " ctladm help\n" @@ -4093,6 +4503,15 @@ main(int argc, char **argv) case CTLADM_CMD_MODIFY: retval = cctl_modify_lun(fd, argc, argv, combinedopt); break; + case CTLADM_CMD_ISLIST: + retval = cctl_islist(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISLOGOUT: + retval = cctl_islogout(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISTERMINATE: + retval = cctl_isterminate(fd, argc, argv, combinedopt); + break; case CTLADM_CMD_HELP: default: usage(retval); diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile new file mode 100644 index 0000000..e6c292d --- /dev/null +++ b/usr.sbin/ctld/Makefile @@ -0,0 +1,21 @@ +# $FreeBSD$ + +PROG= ctld +SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys +CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi +#CFLAGS+= -DICL_KERNEL_PROXY +MAN= ctld.8 ctl.conf.5 + +DPADD= ${LIBCAM} ${LIBSBUF} ${LIBBSDXML} ${LIBUTIL} +LDADD= -lbsdxml -lcam -lcrypto -lfl -lsbuf -lssl -lutil + +YFLAGS+= -v +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctld/ctl.conf.5 b/usr.sbin/ctld/ctl.conf.5 new file mode 100644 index 0000000..fb8bc2a --- /dev/null +++ b/usr.sbin/ctld/ctl.conf.5 @@ -0,0 +1,244 @@ +.\" 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 April 24, 2013 +.Dt CTL.CONF 5 +.Os +.Sh NAME +.Nm ctl.conf +.Nd CAM Target Layer / iSCSI target daemon configuration file +.Sh DESCRIPTION +The +.Nm +configuration file is used by the +.Xr ctld 8 +daemon. +Lines starting with +.Ql # +and empty lines are interpreted as comments. +The general syntax of the +.Nm +file is: +.Bd -literal -offset indent +pidfile <path> + +auth-group <name> { + chap <user> <secret> + ... +} + +portal-group <name> { + listen <address> + listen-iser <address> + discovery-auth-group <name> + ... +} + +target <name> { + auth-group <name> + portal-group <name> + lun <number> { + path <path> + } + ... +} +.Ed +.Ss global level +The following statements are available at the global level: +.Bl -tag -width indent +.It Ic auth-group Aq Ar name +Opens an auth-group section, defining an authentication group, +which can then be assigned to any number of targets. +.It Ic debug Aq Ar level +Specifies debug level. +The default is 0. +.It Ic maxproc Aq Ar number +Specifies limit for concurrently running child processes handling +incoming connections. +The default is 30. +Setting it to 0 disables the limit. +.It Ic pidfile Aq Ar path +Specifies path to pidfile. +The default is +.Pa /var/run/ctld.pid . +.It Ic portal-group Aq Ar name +Opens a portal-group section, defining a portal group, +which can then be assigned to any number of targets. +.It Ic target Aq Ar name +Opens a target configuration section. +.It Ic timeout Aq 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 +.Ss auth-grup level +The following statements are available at the auth-group level: +.Bl -tag -width indent +.It Ic chap Ao Ar user Ac Aq Ar secret +Specifies CHAP authentication credentials. +.It Ic chap-mutual Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret +Specifies mutual CHAP authentication credentials. +Note that for any auth-group, configuration may contain either chap, +or chap-mutual entries; it's an error to mix them. +.El +.Ss portal-group level +The following statements are available at the portal-group level: +.Bl -tag -width indent +.It Ic discovery-auth-group Aq Ar name +Assigns previously defined authentication group to that portal group, +to be used for target discovery. +By default, the discovery will be denied. +A special auth-group, "no-authentication", may be used to allow for discovery +without authentication. +.It Ic listen Aq Ar address +Specifies IPv4 or IPv6 address and port to listen on for incoming connections. +.It Ic listen-iser Aq Ar address +Specifies IPv4 or IPv6 address and port to listen on for incoming connections +using iSER (iSCSI over RDMA) protocol. +.El +.Ss target level: +The following statements are available at the target level: +.Bl -tag -width indent +.It Ic alias Aq Ar text +Assigns human-readable description to that target. +There is no default. +.It Ic auth-group Aq Ar name +Assigns previously defined authentication group to that target. +There is no default; every target must use either auth-group, +or chap, or chap-mutual statements. +A special auth-group, "no-authentication", may be used to permit access +without authentication. +.It Ic chap Ao Ar user Ac Aq Ar secret +Specifies CHAP authentication credentials. +Note that targets must use either auth-group, or chap, +or chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic chap-mutual Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret +Specifies mutual CHAP authentication credentials. +Note that targets must use either auth-group, chap, +chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic portal-group Aq Ar name +Assigns previously defined portal group to that target. +Default portal group is "default", which makes the target available +on TCP port 3260 on all configured IPv4 and IPv6 addresses. +.It Ic lun Aq Ar number +Opens a lun configuration section, defining LUN exported by a target. +.El +.Ss lun level +The following statements are available at the lun level: +.Bl -tag -width indent +.It Ic backend Ao Ar block | Ar ramdisk Ac +Specifies the CTL backend to use for a given LUN. +Valid choices are +.Dq block +and +.Dq ramdisk ; +block is used for LUNs backed +by files in the filesystem; ramdisk is a bitsink device, used mostly for +testing. +The default backend is block. +.It Ic blocksize Aq Ar size +Specifies blocksize visible to the initiator. +The default blocksize is 512. +.It Ic device-id Aq Ar string +Specifies SCSI Device Identification string presented to the initiator. +.It Ic option Ao Ar name Ac Aq Ar value +Specifies CTL-specific options passed to the kernel. +.It Ic path Aq Ar path +Specifies path to file used to back the LUN. +.It Ic serial Aq Ar string +Specifies SCSI serial number presented to the initiator. +.It Ic size Aq Ar size +Specifies LUN size, in bytes. +.El +.Sh FILES +.Bl -tag -width ".Pa /etc/ctl.conf" -compact +.It Pa /etc/ctl.conf +The default location of the +.Xr ctld 8 +configuration file. +.El +.Sh EXAMPLES +.Bd -literal +pidfile /var/run/ctld.pid + +auth-group example2 { + chap-mutual "user" "secret" "mutualuser" "mutualsecret" + chap-mutual "user2" "secret2" "mutualuser" "mutualsecret" +} + +portal-group example2 { + discovery-auth-group no-authentication + listen 127.0.0.1 + listen 0.0.0.0:3261 + listen [::]:3261 + listen [fe80::be:ef] +} + +target iqn.2012-06.com.example:target0 { + alias "Testing target" + auth-group no-authentication + lun 0 { + path /dev/zvol/example_0 + blocksize 4096 + size 4G + } +} + +target iqn.2012-06.com.example:target3 { + chap chapuser chapsecret + lun 0 { + path /dev/zvol/example_3 + } +} + +target iqn.2012-06.com.example:target2 { + auth-group example2 + portal-group example2 + lun 0 { + path /dev/zvol/example2_0 + } + lun 1 { + path /dev/zvol/example2_1 + option foo bar + } +} +.Ed +.Sh SEE ALSO +.Xr ctl 4 , +.Xr ctladm 8 , +.Xr ctld 8 +.Sh AUTHORS +The +.Nm +configuration file functionality for +.Xr ctld 8 +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff --git a/usr.sbin/ctld/ctld.8 b/usr.sbin/ctld/ctld.8 new file mode 100644 index 0000000..060cd9f --- /dev/null +++ b/usr.sbin/ctld/ctld.8 @@ -0,0 +1,114 @@ +.\" 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 CTLD 8 +.Os +.Sh NAME +.Nm ctld +.Nd CAM Target Layer / iSCSI target daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl f Ar config-file +.Sh DESCRIPTION +The +.Nm +daemon is responsible for managing the CAM Target Layer configuration, +accepting incoming iSCSI connections, performing authentication and +passing connections to the kernel part of the native iSCSI target. +.Pp +.Pp +Upon startup, the +.Nm +daemon parses the configuration file and exits, if it encounters any errors. +Then it compares the configuration with the kernel list of LUNs managed +by previously running +.Nm +instances, removes LUNs no longer existing in the configuration file, +and creates new LUNs as neccessary. +After that it listens for the incoming iSCSI connections, performs +authentication, and, if successful, passes the connections to the kernel part +of CTL iSCSI target, which handles it from that point. +.Pp +When it receives a SIGHUP signal, the +.Nm +reloads its configuration and applies the changes to the kernel. +Changes are applied in a way that avoids unneccessary disruptions; +for example removing one LUN does not affect other LUNs. +.Pp +When exiting gracefully, the +.Nm +daemon removes LUNs it managed and forcibly disconnects all the clients. +Otherwise - e.g. when killed with SIGKILL - LUNs stay configured +and clients remain connected. +.Pp +To perform administrative actions that apply to already connected +sessions, such as forcing termination, use +.Xr ctladm 8 . +.Pp +The following options are available: +.Bl -tag -width ".Fl P Ar pidfile" +.It Fl f Ar config-file +Specifies the name of the configuration file. +The default is +.Pa /etc/ctl.conf . +.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 target. +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/ctld.pid" -compact +.It Pa /etc/ctl.conf +The configuration file for +.Nm . +The file format and configuration options are described in +.Xr ctl.conf 5 . +.It Pa /var/run/ctld.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 ctl 4 , +.Xr ctl.conf 5 , +.Xr ctladm 8 +.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/ctld/ctld.c b/usr.sbin/ctld/ctld.c new file mode 100644 index 0000000..34fd650 --- /dev/null +++ b/usr.sbin/ctld/ctld.c @@ -0,0 +1,1715 @@ +/*- + * 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/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "ctld.h" + +static volatile bool sighup_received = false; +static volatile bool sigterm_received = false; +static volatile bool sigalrm_received = false; + +static int nchildren = 0; + +static void +usage(void) +{ + + fprintf(stderr, "usage: ctld [-d][-f config-file]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +struct conf * +conf_new(void) +{ + struct conf *conf; + + conf = calloc(1, sizeof(*conf)); + if (conf == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&conf->conf_targets); + TAILQ_INIT(&conf->conf_auth_groups); + TAILQ_INIT(&conf->conf_portal_groups); + + conf->conf_debug = 0; + conf->conf_timeout = 60; + conf->conf_maxproc = 30; + + return (conf); +} + +void +conf_delete(struct conf *conf) +{ + struct target *targ, *tmp; + struct auth_group *ag, *cagtmp; + struct portal_group *pg, *cpgtmp; + + assert(conf->conf_pidfh == NULL); + + 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); + free(conf->conf_pidfile_path); + free(conf); +} + +static struct auth * +auth_new(struct auth_group *ag) +{ + struct auth *auth; + + auth = 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(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); +} + +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)); + if (ag == NULL) + log_err(1, "calloc"); + if (name != NULL) + ag->ag_name = checked_strdup(name); + TAILQ_INIT(&ag->ag_auths); + 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, *tmp; + + TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next); + + TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, tmp) + auth_delete(auth); + free(ag->ag_name); + free(ag); +} + +struct auth_group * +auth_group_find(struct conf *conf, const char *name) +{ + struct auth_group *ag; + + 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 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_iqn); + } + 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 16 characters " + "long", auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + + if (auth->a_mutual_secret != NULL) { + len = strlen(auth->a_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_iqn); + } + 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 16 characters long", + auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + } +} + +const struct auth * +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_iqn); + return (NULL); + } + + auth = auth_new(ag); + auth->a_user = checked_strdup(user); + auth->a_secret = checked_strdup(secret); + + auth_check_secret_length(auth); + + return (auth); +} + +const struct auth * +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_iqn); + return (NULL); + } + + 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 (auth); +} + +static struct portal * +portal_new(struct portal_group *pg) +{ + struct portal *portal; + + portal = 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); + 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; + + if (name != NULL) { + pg = portal_group_find(conf, name); + if (pg != NULL) { + log_warnx("duplicated portal-group \"%s\"", name); + return (NULL); + } + } + + pg = calloc(1, sizeof(*pg)); + if (pg == NULL) + log_err(1, "calloc"); + pg->pg_name = checked_strdup(name); + TAILQ_INIT(&pg->pg_portals); + pg->pg_conf = conf; + conf->conf_last_portal_group_tag++; + pg->pg_tag = conf->conf_last_portal_group_tag; + TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next); + + return (pg); +} + +void +portal_group_delete(struct portal_group *pg) +{ + struct portal *portal, *tmp; + + TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next); + + TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp) + portal_delete(portal); + free(pg->pg_name); + free(pg); +} + +struct portal_group * +portal_group_find(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); +} + +int +portal_group_add_listen(struct portal_group *pg, const char *value, bool iser) +{ + struct addrinfo hints; + struct portal *portal; + char *addr, *ch, *arg; + const char *port; + int error, colons = 0; + +#ifndef ICL_KERNEL_PROXY + if (iser) { + log_warnx("ctld(8) compiled without ICL_KERNEL_PROXY " + "does not support iSER protocol"); + return (-1); + } +#endif + + portal = portal_new(pg); + portal->p_listen = checked_strdup(value); + portal->p_iser = iser; + + arg = portal->p_listen; + if (arg[0] == '\0') { + log_warnx("empty listen address"); + free(portal->p_listen); + free(portal); + return (1); + } + if (arg[0] == '[') { + /* + * IPv6 address in square brackets, perhaps with port. + */ + arg++; + addr = strsep(&arg, "]"); + if (arg == NULL) { + log_warnx("invalid listen address %s", + portal->p_listen); + free(portal->p_listen); + free(portal); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid listen address %s", + portal->p_listen); + free(portal->p_listen); + free(portal); + 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, &portal->p_ai); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + portal->p_listen, gai_strerror(error)); + free(portal->p_listen); + free(portal); + return (1); + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + return (0); +} + +static bool +valid_hex(const char ch) +{ + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + return (true); + default: + return (false); + } +} + +bool +valid_iscsi_name(const char *name) +{ + int i; + + if (strlen(name) >= MAX_NAME_LEN) { + log_warnx("overlong name for target \"%s\"; max length allowed " + "by iSCSI specification is %d characters", + name, MAX_NAME_LEN); + return (false); + } + + /* + * In the cases below, we don't return an error, just in case the admin + * was right, and we're wrong. + */ + if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) { + for (i = strlen("iqn."); name[i] != '\0'; i++) { + /* + * XXX: We should verify UTF-8 normalisation, as defined + * by 3.2.6.2: iSCSI Name Encoding. + */ + if (isalnum(name[i])) + continue; + if (name[i] == '-' || name[i] == '.' || name[i] == ':') + continue; + log_warnx("invalid character \"%c\" in target name " + "\"%s\"; allowed characters are letters, digits, " + "'-', '.', and ':'", name[i], name); + break; + } + /* + * XXX: Check more stuff: valid date and a valid reversed domain. + */ + } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) { + if (strlen(name) != strlen("eui.") + 16) + log_warnx("invalid target name \"%s\"; the \"eui.\" " + "should be followed by exactly 16 hexadecimal " + "digits", name); + for (i = strlen("eui."); name[i] != '\0'; i++) { + if (!valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) { + if (strlen(name) > strlen("naa.") + 32) + log_warnx("invalid target name \"%s\"; the \"naa.\" " + "should be followed by at most 32 hexadecimal " + "digits", name); + for (i = strlen("naa."); name[i] != '\0'; i++) { + if (!valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else { + log_warnx("invalid target name \"%s\"; should start with " + "either \".iqn\", \"eui.\", or \"naa.\"", + name); + } + return (true); +} + +struct target * +target_new(struct conf *conf, const char *iqn) +{ + struct target *targ; + int i, len; + + targ = target_find(conf, iqn); + if (targ != NULL) { + log_warnx("duplicated target \"%s\"", iqn); + return (NULL); + } + if (valid_iscsi_name(iqn) == false) { + log_warnx("target name \"%s\" is invalid", iqn); + return (NULL); + } + targ = calloc(1, sizeof(*targ)); + if (targ == NULL) + log_err(1, "calloc"); + targ->t_iqn = checked_strdup(iqn); + + /* + * RFC 3722 requires us to normalize the name to lowercase. + */ + len = strlen(iqn); + for (i = 0; i < len; i++) + targ->t_iqn[i] = tolower(targ->t_iqn[i]); + + TAILQ_INIT(&targ->t_luns); + targ->t_conf = conf; + TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); + + return (targ); +} + +void +target_delete(struct target *targ) +{ + struct lun *lun, *tmp; + + TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); + + TAILQ_FOREACH_SAFE(lun, &targ->t_luns, l_next, tmp) + lun_delete(lun); + free(targ->t_iqn); + free(targ); +} + +struct target * +target_find(struct conf *conf, const char *iqn) +{ + struct target *targ; + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (strcasecmp(targ->t_iqn, iqn) == 0) + return (targ); + } + + return (NULL); +} + +struct lun * +lun_new(struct target *targ, int lun_id) +{ + struct lun *lun; + + lun = lun_find(targ, lun_id); + if (lun != NULL) { + log_warnx("duplicated lun %d for target \"%s\"", + lun_id, targ->t_iqn); + return (NULL); + } + + lun = calloc(1, sizeof(*lun)); + if (lun == NULL) + log_err(1, "calloc"); + lun->l_lun = lun_id; + TAILQ_INIT(&lun->l_options); + lun->l_target = targ; + TAILQ_INSERT_TAIL(&targ->t_luns, lun, l_next); + + return (lun); +} + +void +lun_delete(struct lun *lun) +{ + struct lun_option *lo, *tmp; + + TAILQ_REMOVE(&lun->l_target->t_luns, lun, l_next); + + TAILQ_FOREACH_SAFE(lo, &lun->l_options, lo_next, tmp) + lun_option_delete(lo); + free(lun->l_backend); + free(lun->l_device_id); + free(lun->l_path); + free(lun->l_serial); + free(lun); +} + +struct lun * +lun_find(struct target *targ, int lun_id) +{ + struct lun *lun; + + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + if (lun->l_lun == lun_id) + return (lun); + } + + return (NULL); +} + +void +lun_set_backend(struct lun *lun, const char *value) +{ + free(lun->l_backend); + lun->l_backend = checked_strdup(value); +} + +void +lun_set_blocksize(struct lun *lun, size_t value) +{ + + lun->l_blocksize = value; +} + +void +lun_set_device_id(struct lun *lun, const char *value) +{ + free(lun->l_device_id); + lun->l_device_id = checked_strdup(value); +} + +void +lun_set_path(struct lun *lun, const char *value) +{ + free(lun->l_path); + lun->l_path = checked_strdup(value); +} + +void +lun_set_serial(struct lun *lun, const char *value) +{ + free(lun->l_serial); + lun->l_serial = checked_strdup(value); +} + +void +lun_set_size(struct lun *lun, size_t value) +{ + + lun->l_size = value; +} + +void +lun_set_ctl_lun(struct lun *lun, uint32_t value) +{ + + lun->l_ctl_lun = value; +} + +struct lun_option * +lun_option_new(struct lun *lun, const char *name, const char *value) +{ + struct lun_option *lo; + + lo = lun_option_find(lun, name); + if (lo != NULL) { + log_warnx("duplicated lun option %s for lun %d, target \"%s\"", + name, lun->l_lun, lun->l_target->t_iqn); + return (NULL); + } + + lo = calloc(1, sizeof(*lo)); + if (lo == NULL) + log_err(1, "calloc"); + lo->lo_name = checked_strdup(name); + lo->lo_value = checked_strdup(value); + lo->lo_lun = lun; + TAILQ_INSERT_TAIL(&lun->l_options, lo, lo_next); + + return (lo); +} + +void +lun_option_delete(struct lun_option *lo) +{ + + TAILQ_REMOVE(&lo->lo_lun->l_options, lo, lo_next); + + free(lo->lo_name); + free(lo->lo_value); + free(lo); +} + +struct lun_option * +lun_option_find(struct lun *lun, const char *name) +{ + struct lun_option *lo; + + TAILQ_FOREACH(lo, &lun->l_options, lo_next) { + if (strcmp(lo->lo_name, name) == 0) + return (lo); + } + + return (NULL); +} + +void +lun_option_set(struct lun_option *lo, const char *value) +{ + + free(lo->lo_value); + lo->lo_value = checked_strdup(value); +} + +static struct connection * +connection_new(struct portal *portal, int fd, const char *host) +{ + struct connection *conn; + + conn = calloc(1, sizeof(*conn)); + if (conn == NULL) + log_err(1, "calloc"); + conn->conn_portal = portal; + conn->conn_socket = fd; + conn->conn_initiator_addr = checked_strdup(host); + + /* + * Default values, from RFC 3720, section 12. + */ + conn->conn_max_data_segment_length = 8192; + conn->conn_max_burst_length = 262144; + conn->conn_immediate_data = true; + + return (conn); +} + +#if 0 +static void +conf_print(struct conf *conf) +{ + struct auth_group *ag; + struct auth *auth; + struct portal_group *pg; + struct portal *portal; + struct target *targ; + struct lun *lun; + struct lun_option *lo; + + 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); + 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); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + fprintf(stderr, "target %s {\n", targ->t_iqn); + if (targ->t_alias != NULL) + fprintf(stderr, "\t alias %s\n", targ->t_alias); + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + fprintf(stderr, "\tlun %d {\n", lun->l_lun); + fprintf(stderr, "\t\tpath %s\n", lun->l_path); + TAILQ_FOREACH(lo, &lun->l_options, lo_next) + fprintf(stderr, "\t\toption %s %s\n", + lo->lo_name, lo->lo_value); + fprintf(stderr, "\t}\n"); + } + fprintf(stderr, "}\n"); + } +} +#endif + +int +conf_verify(struct conf *conf) +{ + struct auth_group *ag; + struct portal_group *pg; + struct target *targ; + struct lun *lun, *lun2; + bool found_lun0; + + if (conf->conf_pidfile_path == NULL) + conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE); + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_auth_group == NULL) { + log_warnx("missing authentication for target \"%s\"; " + "must specify either \"auth-group\", \"chap\", " + "or \"chap-mutual\"", targ->t_iqn); + return (1); + } + if (targ->t_portal_group == NULL) { + targ->t_portal_group = portal_group_find(conf, + "default"); + assert(targ->t_portal_group != NULL); + } + found_lun0 = false; + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + if (lun->l_lun == 0) + found_lun0 = true; + if (lun->l_backend == NULL) + lun_set_backend(lun, "block"); + if (strcmp(lun->l_backend, "block") == 0 && + lun->l_path == NULL) { + log_warnx("missing path for lun %d, " + "target \"%s\"", lun->l_lun, targ->t_iqn); + return (1); + } + if (strcmp(lun->l_backend, "ramdisk") == 0) { + if (lun->l_size == 0) { + log_warnx("missing size for " + "ramdisk-backed lun %d, " + "target \"%s\"", + lun->l_lun, targ->t_iqn); + return (1); + } + if (lun->l_path != NULL) { + log_warnx("path must not be specified " + "for ramdisk-backed lun %d, " + "target \"%s\"", + lun->l_lun, targ->t_iqn); + return (1); + } + } + if (lun->l_lun < 0 || lun->l_lun > 255) { + log_warnx("invalid lun number for lun %d, " + "target \"%s\"; must be between 0 and 255", + lun->l_lun, targ->t_iqn); + return (1); + } +#if 1 /* Should we? */ + TAILQ_FOREACH(lun2, &targ->t_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: duplicate path " + "for lun %d, target \"%s\"", + lun->l_lun, targ->t_iqn); + } +#endif + if (lun->l_blocksize == 0) { + lun_set_blocksize(lun, DEFAULT_BLOCKSIZE); + } else if (lun->l_blocksize <= 0) { + log_warnx("invalid blocksize for lun %d, " + "target \"%s\"; must be larger than 0", + lun->l_lun, targ->t_iqn); + return (1); + } + if (lun->l_size != 0 && + lun->l_size % lun->l_blocksize != 0) { + log_warnx("invalid size for lun %d, target " + "\"%s\"; must be multiple of blocksize", + lun->l_lun, targ->t_iqn); + return (1); + } + } + if (!found_lun0) { + log_warnx("mandatory LUN 0 not configured " + "for target \"%s\"", targ->t_iqn); + return (1); + } + } + 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, "no-access"); + assert(pg->pg_discovery_auth_group != NULL); + } + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_portal_group == pg) + break; + } + if (targ == NULL) { + if (strcmp(pg->pg_name, "default") != 0) + log_warnx("portal-group \"%s\" not assigned " + "to any target", pg->pg_name); + pg->pg_unassigned = true; + } else + pg->pg_unassigned = false; + } + 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); + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_auth_group == ag) + break; + } + if (targ == NULL && ag->ag_name != NULL && + 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 (0); +} + +static int +conf_apply(struct conf *oldconf, struct conf *newconf) +{ + struct target *oldtarg, *newtarg, *tmptarg; + struct lun *oldlun, *newlun, *tmplun; + struct portal_group *oldpg, *newpg; + struct portal *oldp, *newp; + pid_t otherpid; + int changed, cumulated_error = 0, error; +#ifndef ICL_KERNEL_PROXY + int one = 1; +#endif + + 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_pidfh != NULL) { + assert(oldconf->conf_pidfile_path != NULL); + if (newconf->conf_pidfile_path != NULL && + strcmp(oldconf->conf_pidfile_path, + newconf->conf_pidfile_path) == 0) { + newconf->conf_pidfh = oldconf->conf_pidfh; + oldconf->conf_pidfh = NULL; + } else { + log_debugx("removing pidfile %s", + oldconf->conf_pidfile_path); + pidfile_remove(oldconf->conf_pidfh); + oldconf->conf_pidfh = NULL; + } + } + + if (newconf->conf_pidfh == NULL && 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); + } + } + + TAILQ_FOREACH_SAFE(oldtarg, &oldconf->conf_targets, t_next, tmptarg) { + /* + * First, remove any targets present in the old configuration + * and missing in the new one. + */ + newtarg = target_find(newconf, oldtarg->t_iqn); + if (newtarg == NULL) { + TAILQ_FOREACH_SAFE(oldlun, &oldtarg->t_luns, l_next, + tmplun) { + log_debugx("target %s not found in the " + "configuration file; removing its lun %d, " + "backed by CTL lun %d", + oldtarg->t_iqn, oldlun->l_lun, + oldlun->l_ctl_lun); + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + } + target_delete(oldtarg); + continue; + } + + /* + * Second, remove any LUNs present in the old target + * and missing in the new one. + */ + TAILQ_FOREACH_SAFE(oldlun, &oldtarg->t_luns, l_next, tmplun) { + newlun = lun_find(newtarg, oldlun->l_lun); + if (newlun == NULL) { + log_debugx("lun %d, target %s, CTL lun %d " + "not found in the configuration file; " + "removing", oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + 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 %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (oldlun->l_blocksize != newlun->l_blocksize) { + log_debugx("blocksize for lun %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + 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 %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + 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 %d, target %s, " + "CTL lun %d, changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + 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 %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (changed) { + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + continue; + } + + lun_set_ctl_lun(newlun, oldlun->l_ctl_lun); + } + } + + /* + * Now add new targets or modify existing ones. + */ + TAILQ_FOREACH(newtarg, &newconf->conf_targets, t_next) { + oldtarg = target_find(oldconf, newtarg->t_iqn); + + TAILQ_FOREACH(newlun, &newtarg->t_luns, l_next) { + if (oldtarg != NULL) { + oldlun = lun_find(oldtarg, newlun->l_lun); + if (oldlun != NULL) { + if (newlun->l_size != oldlun->l_size) { + log_debugx("resizing lun %d, " + "target %s, CTL lun %d", + newlun->l_lun, + newtarg->t_iqn, + newlun->l_ctl_lun); + error = + kernel_lun_resize(newlun); + if (error != 0) { + log_warnx("failed to " + "resize lun %d, " + "target %s, " + "CTL lun %d", + newlun->l_lun, + newtarg->t_iqn, + newlun->l_lun); + cumulated_error++; + } + } + continue; + } + } + log_debugx("adding lun %d, target %s", + newlun->l_lun, newtarg->t_iqn); + error = kernel_lun_add(newlun); + if (error != 0) { + log_warnx("failed to add lun %d, target %s", + newlun->l_lun, newtarg->t_iqn); + cumulated_error++; + } + } + } + + /* + * Go through the new portals, opening the sockets as neccessary. + */ + TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { + 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 (strcmp(newp->p_listen, + oldp->p_listen) == 0 && + oldp->p_socket > 0) { + newp->p_socket = + oldp->p_socket; + oldp->p_socket = 0; + break; + } + } + } + if (newp->p_socket > 0) { + /* + * We're done with this portal. + */ + continue; + } + +#ifdef ICL_KERNEL_PROXY + log_debugx("listening on %s, portal-group \"%s\" using ICL proxy", + newp->p_listen, newpg->pg_name); + kernel_listen(newp->p_ai, newp->p_iser); +#else + assert(newp->p_iser == false); + + log_debugx("listening on %s, portal-group \"%s\"", + newp->p_listen, newpg->pg_name); + newp->p_socket = socket(newp->p_ai->ai_family, + newp->p_ai->ai_socktype, + newp->p_ai->ai_protocol); + if (newp->p_socket < 0) { + log_warn("socket(2) failed for %s", + newp->p_listen); + cumulated_error++; + continue; + } + error = setsockopt(newp->p_socket, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one)); + if (error != 0) { + log_warn("setsockopt(SO_REUSEADDR) failed " + "for %s", newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } + error = bind(newp->p_socket, newp->p_ai->ai_addr, + newp->p_ai->ai_addrlen); + if (error != 0) { + log_warn("bind(2) failed for %s", + newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } + error = listen(newp->p_socket, -1); + if (error != 0) { + log_warn("listen(2) failed for %s", + newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } +#endif /* !ICL_KERNEL_PROXY */ + } + } + + /* + * 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; + } + } + + return (cumulated_error); +} + +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(const struct conf *conf) +{ + struct sigaction sa; + struct itimerval itv; + int error; + + if (conf->conf_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 = conf->conf_timeout; + + log_debugx("setting session timeout to %d seconds", + conf->conf_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, bool dont_fork) +{ + struct connection *conn; +#ifndef ICL_KERNEL_PROXY + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + int error; +#endif + 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); + +#ifdef ICL_KERNEL_PROXY + /* + * XXX + */ + log_set_peer_addr("XXX"); +#else + error = getpeername(fd, (struct sockaddr *)&ss, &sslen); + if (error != 0) + log_err(1, "getpeername"); + error = getnameinfo((struct sockaddr *)&ss, sslen, + host, sizeof(host), NULL, 0, NI_NUMERICHOST); + if (error != 0) + log_errx(1, "getaddrinfo: %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); +#endif + + conn = connection_new(portal, fd, host); + set_timeout(conf); + 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); +} + +#ifndef ICL_KERNEL_PROXY +static int +fd_add(int fd, fd_set *fdset, int nfds) +{ + + /* + * Skip sockets which we failed to bind. + */ + if (fd <= 0) + return (nfds); + + FD_SET(fd, fdset); + if (fd > nfds) + nfds = fd; + return (nfds); +} +#endif + +static void +main_loop(struct conf *conf, bool dont_fork) +{ + struct portal_group *pg; + struct portal *portal; +#ifdef ICL_KERNEL_PROXY + int connection_id; +#else + fd_set fdset; + int error, nfds, client_fd; +#endif + + pidfile_write(conf->conf_pidfh); + + for (;;) { + if (sighup_received || sigterm_received) + return; + +#ifdef ICL_KERNEL_PROXY + connection_id = kernel_accept(); + if (connection_id == 0) + continue; + + /* + * XXX: This is obviously temporary. + */ + pg = TAILQ_FIRST(&conf->conf_portal_groups); + portal = TAILQ_FIRST(&pg->pg_portals); + + handle_connection(portal, connection_id, dont_fork); +#else + FD_ZERO(&fdset); + nfds = 0; + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + TAILQ_FOREACH(portal, &pg->pg_portals, p_next) + nfds = fd_add(portal->p_socket, &fdset, nfds); + } + error = select(nfds + 1, &fdset, NULL, NULL, NULL); + if (error <= 0) { + if (errno == EINTR) + return; + log_err(1, "select"); + } + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { + if (!FD_ISSET(portal->p_socket, &fdset)) + continue; + client_fd = accept(portal->p_socket, NULL, 0); + if (client_fd < 0) + log_err(1, "accept"); + handle_connection(portal, client_fd, dont_fork); + break; + } + } +#endif /* !ICL_KERNEL_PROXY */ + } +} + +static void +sighup_handler(int dummy __unused) +{ + + sighup_received = true; +} + +static void +sigterm_handler(int dummy __unused) +{ + + sigterm_received = true; +} + +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"); +} + +int +main(int argc, char **argv) +{ + struct conf *oldconf, *newconf, *tmpconf; + const char *config_path = DEFAULT_CONFIG_PATH; + int debug = 0, ch, error; + bool dont_daemonize = false; + + while ((ch = getopt(argc, argv, "df:")) != -1) { + switch (ch) { + case 'd': + dont_daemonize = true; + debug++; + break; + case 'f': + config_path = optarg; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + log_init(debug); + kernel_init(); + + oldconf = conf_new_from_kernel(); + newconf = conf_new_from_file(config_path); + if (newconf == NULL) + log_errx(1, "configuration error, exiting"); + if (debug > 0) { + oldconf->conf_debug = debug; + newconf->conf_debug = debug; + } + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(newconf->conf_pidfh); + exit(1); + } + } + +#ifdef ICL_KERNEL_PROXY + log_debugx("enabling CTL iSCSI port"); + error = kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); +#endif + + error = conf_apply(oldconf, newconf); + if (error != 0) + log_errx(1, "failed to apply configuration, exiting"); + conf_delete(oldconf); + oldconf = NULL; + + register_signals(); + +#ifndef ICL_KERNEL_PROXY + log_debugx("enabling CTL iSCSI port"); + error = kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); +#endif + + for (;;) { + main_loop(newconf, dont_daemonize); + if (sighup_received) { + sighup_received = false; + log_debugx("received SIGHUP, reloading configuration"); + tmpconf = conf_new_from_file(config_path); + if (tmpconf == NULL) { + log_warnx("configuration error, " + "continuing with old configuration"); + } 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("disabling CTL iSCSI port " + "and terminating all connections"); + error = kernel_port_off(); + if (error != 0) + log_warnx("failed to disable CTL iSCSI port"); + + 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"); + + log_warnx("exiting on signal"); + exit(0); + } else { + nchildren -= wait_for_children(false); + assert(nchildren >= 0); + } + } + /* NOTREACHED */ +} diff --git a/usr.sbin/ctld/ctld.h b/usr.sbin/ctld/ctld.h new file mode 100644 index 0000000..3557978 --- /dev/null +++ b/usr.sbin/ctld/ctld.h @@ -0,0 +1,277 @@ +/*- + * 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 CTLD_H +#define CTLD_H + +#include <sys/queue.h> +#include <stdbool.h> +#include <libutil.h> + +#define DEFAULT_CONFIG_PATH "/etc/ctl.conf" +#define DEFAULT_PIDFILE "/var/run/ctld.pid" +#define DEFAULT_BLOCKSIZE 512 + +#define MAX_NAME_LEN 223 +#define MAX_DATA_SEGMENT_LENGTH (128 * 1024) +#define MAX_BURST_LENGTH 16776192 + +struct auth { + TAILQ_ENTRY(auth) a_next; + struct auth_group *a_auth_group; + char *a_user; + char *a_secret; + char *a_mutual_user; + char *a_mutual_secret; +}; + +#define AG_TYPE_UNKNOWN 0 +#define AG_TYPE_NO_AUTHENTICATION 1 +#define AG_TYPE_CHAP 2 +#define AG_TYPE_CHAP_MUTUAL 3 + +struct auth_group { + TAILQ_ENTRY(auth_group) ag_next; + struct conf *ag_conf; + char *ag_name; + struct target *ag_target; + int ag_type; + TAILQ_HEAD(, auth) ag_auths; +}; + +struct portal { + TAILQ_ENTRY(portal) p_next; + struct portal_group *p_portal_group; + bool p_iser; + char *p_listen; + struct addrinfo *p_ai; + + TAILQ_HEAD(, target) p_targets; + int p_socket; +}; + +struct portal_group { + TAILQ_ENTRY(portal_group) pg_next; + struct conf *pg_conf; + char *pg_name; + struct auth_group *pg_discovery_auth_group; + bool pg_unassigned; + TAILQ_HEAD(, portal) pg_portals; + + uint16_t pg_tag; +}; + +struct lun_option { + TAILQ_ENTRY(lun_option) lo_next; + struct lun *lo_lun; + char *lo_name; + char *lo_value; +}; + +struct lun { + TAILQ_ENTRY(lun) l_next; + TAILQ_HEAD(, lun_option) l_options; + struct target *l_target; + int l_lun; + char *l_backend; + int l_blocksize; + char *l_device_id; + char *l_path; + char *l_serial; + int64_t l_size; + + int l_ctl_lun; +}; + +struct target { + TAILQ_ENTRY(target) t_next; + TAILQ_HEAD(, lun) t_luns; + struct conf *t_conf; + struct auth_group *t_auth_group; + struct portal_group *t_portal_group; + char *t_iqn; + char *t_alias; +}; + +struct conf { + char *conf_pidfile_path; + TAILQ_HEAD(, target) conf_targets; + TAILQ_HEAD(, auth_group) conf_auth_groups; + TAILQ_HEAD(, portal_group) conf_portal_groups; + int conf_debug; + int conf_timeout; + int conf_maxproc; + + uint16_t conf_last_portal_group_tag; + struct pidfh *conf_pidfh; +}; + +#define CONN_SESSION_TYPE_NONE 0 +#define CONN_SESSION_TYPE_DISCOVERY 1 +#define CONN_SESSION_TYPE_NORMAL 2 + +#define CONN_DIGEST_NONE 0 +#define CONN_DIGEST_CRC32C 1 + +struct connection { + struct portal *conn_portal; + struct target *conn_target; + int conn_socket; + int conn_session_type; + char *conn_initiator_name; + char *conn_initiator_addr; + char *conn_initiator_alias; + uint32_t conn_cmdsn; + uint32_t conn_statsn; + size_t conn_max_data_segment_length; + size_t conn_max_burst_length; + int conn_immediate_data; + int conn_header_digest; + int conn_data_digest; +}; + +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 conf *conf_new(void); +struct conf *conf_new_from_file(const char *path); +struct conf *conf_new_from_kernel(void); +void conf_delete(struct conf *conf); +int conf_verify(struct conf *conf); + +struct auth_group *auth_group_new(struct conf *conf, const char *name); +void auth_group_delete(struct auth_group *ag); +struct auth_group *auth_group_find(struct conf *conf, const char *name); + +const struct auth *auth_new_chap(struct auth_group *ag, + const char *user, const char *secret); +const struct auth *auth_new_chap_mutual(struct auth_group *ag, + const char *user, const char *secret, + const char *user2, const char *secret2); +const struct auth *auth_find(struct auth_group *ag, + const char *user); + +struct portal_group *portal_group_new(struct conf *conf, const char *name); +void portal_group_delete(struct portal_group *pg); +struct portal_group *portal_group_find(struct conf *conf, const char *name); +int portal_group_add_listen(struct portal_group *pg, + const char *listen, bool iser); + +struct target *target_new(struct conf *conf, const char *iqn); +void target_delete(struct target *target); +struct target *target_find(struct conf *conf, + const char *iqn); + +struct lun *lun_new(struct target *target, int lun_id); +void lun_delete(struct lun *lun); +struct lun *lun_find(struct target *target, int lun_id); +void lun_set_backend(struct lun *lun, const char *value); +void lun_set_blocksize(struct lun *lun, size_t value); +void lun_set_device_id(struct lun *lun, const char *value); +void lun_set_path(struct lun *lun, const char *value); +void lun_set_serial(struct lun *lun, const char *value); +void lun_set_size(struct lun *lun, size_t value); +void lun_set_ctl_lun(struct lun *lun, uint32_t value); + +struct lun_option *lun_option_new(struct lun *lun, + const char *name, const char *value); +void lun_option_delete(struct lun_option *clo); +struct lun_option *lun_option_find(struct lun *lun, const char *name); +void lun_option_set(struct lun_option *clo, + const char *value); + +void kernel_init(void); +int kernel_lun_add(struct lun *lun); +int kernel_lun_resize(struct lun *lun); +int kernel_lun_remove(struct lun *lun); +void kernel_handoff(struct connection *conn); +int kernel_port_on(void); +int kernel_port_off(void); +void kernel_capsicate(void); + +/* + * ICL_KERNEL_PROXY + */ +void kernel_listen(struct addrinfo *ai, bool iser); +int kernel_accept(void); +void kernel_send(struct pdu *pdu); +void kernel_receive(struct pdu *pdu); + +struct keys *keys_new(void); +void keys_delete(struct keys *keys); +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 *conn); +struct pdu *pdu_new_response(struct pdu *request); +void pdu_delete(struct pdu *pdu); +void pdu_receive(struct pdu *request); +void pdu_send(struct pdu *response); + +void login(struct connection *conn); + +void discovery(struct connection *conn); + +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 valid_iscsi_name(const char *name); +bool timed_out(void); + +#endif /* !CTLD_H */ diff --git a/usr.sbin/ctld/discovery.c b/usr.sbin/ctld/discovery.c new file mode 100644 index 0000000..bef7da6 --- /dev/null +++ b/usr.sbin/ctld/discovery.c @@ -0,0 +1,221 @@ +/*- + * 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 <netinet/in.h> + +#include "ctld.h" +#include "iscsi_proto.h" + +static struct pdu * +text_receive(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = pdu_new(conn); + pdu_receive(request); + if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_TEXT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->pdu_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; +#if 0 + if ((bhstr->bhstr_flags & ISCSI_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 (request->pdu_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + + if (ntohl(bhstr->bhstr_cmdsn) < conn->conn_cmdsn) { + log_errx(1, "received Text PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); + } + if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_expstatsn), + conn->conn_statsn); + } + conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); + + return (request); +} + +static struct pdu * +text_new_response(struct pdu *request) +{ + struct pdu *response; + struct connection *conn; + struct iscsi_bhs_text_request *bhstr; + struct iscsi_bhs_text_response *bhstr2; + + bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; + conn = request->pdu_connection; + + response = pdu_new_response(request); + bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs; + bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; + bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; + bhstr2->bhstr_lun = bhstr->bhstr_lun; + bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; + bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; + bhstr2->bhstr_statsn = htonl(conn->conn_statsn++); + bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); + bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); + + return (response); +} + +static struct pdu * +logout_receive(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = pdu_new(conn); + pdu_receive(request); + if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGOUT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->pdu_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; + if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) + log_debugx("received Logout PDU with invalid reason 0x%x; " + "continuing anyway", bhslr->bhslr_reason & 0x7f); + if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) { + log_errx(1, "received Logout PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + conn->conn_statsn); + } + conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct pdu * +logout_new_response(struct pdu *request) +{ + struct pdu *response; + struct connection *conn; + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; + conn = request->pdu_connection; + + response = pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + 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); +} + +void +discovery(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct target *targ; + const char *send_targets; + + log_debugx("beginning discovery session; waiting for Text PDU"); + request = text_receive(conn); + request_keys = keys_new(); + keys_load(request_keys, request); + + send_targets = keys_find(request_keys, "SendTargets"); + if (send_targets == NULL) + log_errx(1, "received Text PDU without SendTargets"); + + response = text_new_response(request); + response_keys = keys_new(); + + if (strcmp(send_targets, "All") == 0) { + TAILQ_FOREACH(targ, + &conn->conn_portal->p_portal_group->pg_conf->conf_targets, + t_next) { + if (targ->t_portal_group != + conn->conn_portal->p_portal_group) { + log_debugx("not returning target \"%s\"; " + "belongs to a different portal group", + targ->t_iqn); + continue; + } + keys_add(response_keys, "TargetName", targ->t_iqn); + } + } else { + targ = target_find(conn->conn_portal->p_portal_group->pg_conf, + send_targets); + if (targ == NULL) { + log_debugx("initiator requested information on unknown " + "target \"%s\"; returning nothing", send_targets); + } else { + keys_add(response_keys, "TargetName", targ->t_iqn); + } + } + keys_save(response_keys, response); + + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + log_debugx("done sending targets; waiting for Logout PDU"); + request = logout_receive(conn); + response = logout_new_response(request); + + pdu_send(response); + pdu_delete(response); + pdu_delete(request); + + log_debugx("discovery session done"); +} diff --git a/usr.sbin/ctld/kernel.c b/usr.sbin/ctld/kernel.c new file mode 100644 index 0000000..50ddc0c --- /dev/null +++ b/usr.sbin/ctld/kernel.c @@ -0,0 +1,782 @@ +/*- + * Copyright (c) 2003, 2004 Silicon Graphics International Corp. + * Copyright (c) 1997-2007 Kenneth D. Merry + * Copyright (c) 2012 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/queue.h> +#include <sys/callout.h> +#include <sys/sbuf.h> +#include <sys/capability.h> +#include <assert.h> +#include <bsdxml.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> +#include <cam/ctl/ctl.h> +#include <cam/ctl/ctl_io.h> +#include <cam/ctl/ctl_frontend_internal.h> +#include <cam/ctl/ctl_backend.h> +#include <cam/ctl/ctl_ioctl.h> +#include <cam/ctl/ctl_backend_block.h> +#include <cam/ctl/ctl_util.h> +#include <cam/ctl/ctl_scsi_all.h> + +#ifdef ICL_KERNEL_PROXY +#include <netdb.h> +#endif + +#include "ctld.h" + +static int ctl_fd = 0; + +void +kernel_init(void) +{ + int retval, saved_errno; + + ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); + if (ctl_fd < 0) { + 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); +} + +/* + * Name/value pair used for per-LUN attributes. + */ +struct cctl_lun_nv { + char *name; + char *value; + STAILQ_ENTRY(cctl_lun_nv) links; +}; + +/* + * Backend LUN information. + */ +struct cctl_lun { + uint64_t lun_id; + char *backend_type; + uint64_t size_blocks; + uint32_t blocksize; + char *serial_number; + char *device_id; + char *cfiscsi_target; + char *cfiscsi_target_alias; + int cfiscsi_lun; + STAILQ_HEAD(,cctl_lun_nv) attr_list; + STAILQ_ENTRY(cctl_lun) links; +}; + +struct cctl_devlist_data { + int num_luns; + STAILQ_HEAD(,cctl_lun) lun_list; + struct cctl_lun *cur_lun; + 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__, + sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0])); + + 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)); + if (cur_lun == NULL) + log_err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_lun)); + + devlist->num_luns++; + devlist->cur_lun = cur_lun; + + STAILQ_INIT(&cur_lun->attr_list); + 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; + + 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, "size") == 0) { + cur_lun->size_blocks = strtoull(str, NULL, 0); + } else if (strcmp(name, "blocksize") == 0) { + 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, "cfiscsi_target") == 0) { + cur_lun->cfiscsi_target = str; + str = NULL; + } else if (strcmp(name, "cfiscsi_target_alias") == 0) { + cur_lun->cfiscsi_target_alias = str; + str = NULL; + } else if (strcmp(name, "cfiscsi_lun") == 0) { + cur_lun->cfiscsi_lun = strtoul(str, NULL, 0); + } else if (strcmp(name, "lun") == 0) { + devlist->cur_lun = NULL; + } else if (strcmp(name, "ctllunlist") == 0) { + + } else { + struct cctl_lun_nv *nv; + + nv = calloc(1, sizeof(*nv)); + if (nv == NULL) + log_err(1, "%s: can't allocate %zd bytes for nv pair", + __func__, sizeof(*nv)); + + nv->name = checked_strdup(name); + + nv->value = str; + str = NULL; + STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links); + } + + 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(void) +{ + struct conf *conf = NULL; + struct target *targ; + struct lun *cl; + struct lun_option *lo; + struct ctl_lun_list list; + struct cctl_devlist_data devlist; + struct cctl_lun *lun; + XML_Parser parser; + char *lun_str = NULL; + int lun_len; + int retval; + + lun_len = 4096; + + bzero(&devlist, sizeof(devlist)); + STAILQ_INIT(&devlist.lun_list); + + log_debugx("obtaining previously configured CTL luns from the kernel"); + +retry: + lun_str = realloc(lun_str, lun_len); + if (lun_str == NULL) + log_err(1, "realloc"); + + bzero(&list, sizeof(list)); + list.alloc_len = lun_len; + list.status = CTL_LUN_LIST_NONE; + list.lun_xml = lun_str; + + if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) { + log_warn("error issuing CTL_LUN_LIST ioctl"); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_ERROR) { + log_warnx("error returned from CTL_LUN_LIST ioctl: %s", + list.error_str); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { + lun_len = lun_len << 1; + goto retry; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + log_warnx("unable to create XML parser"); + free(lun_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, lun_str, strlen(lun_str), 1); + XML_ParserFree(parser); + free(lun_str); + if (retval != 1) { + log_warnx("XML_Parse failed"); + return (NULL); + } + + conf = conf_new(); + + STAILQ_FOREACH(lun, &devlist.lun_list, links) { + struct cctl_lun_nv *nv; + + if (lun->cfiscsi_target == NULL) { + log_debugx("CTL lun %ju wasn't managed by ctld; " + "ignoring", (uintmax_t)lun->lun_id); + continue; + } + + targ = target_find(conf, lun->cfiscsi_target); + if (targ == NULL) { +#if 0 + log_debugx("found new kernel target %s for CTL lun %ld", + lun->cfiscsi_target, lun->lun_id); +#endif + targ = target_new(conf, lun->cfiscsi_target); + if (targ == NULL) { + log_warnx("target_new failed"); + continue; + } + } + + cl = lun_find(targ, lun->cfiscsi_lun); + if (cl != NULL) { + log_warnx("found CTL lun %ju, backing lun %d, target " + "%s, also backed by CTL lun %d; ignoring", + (uintmax_t) lun->lun_id, cl->l_lun, + cl->l_target->t_iqn, cl->l_ctl_lun); + continue; + } + + log_debugx("found CTL lun %ju, backing lun %d, target %s", + (uintmax_t)lun->lun_id, lun->cfiscsi_lun, lun->cfiscsi_target); + + cl = lun_new(targ, lun->cfiscsi_lun); + if (cl == NULL) { + log_warnx("lun_new failed"); + continue; + } + lun_set_backend(cl, lun->backend_type); + lun_set_blocksize(cl, lun->blocksize); + lun_set_device_id(cl, lun->device_id); + lun_set_serial(cl, lun->serial_number); + lun_set_size(cl, lun->size_blocks * cl->l_blocksize); + lun_set_ctl_lun(cl, lun->lun_id); + + STAILQ_FOREACH(nv, &lun->attr_list, links) { + if (strcmp(nv->name, "file") == 0 || + strcmp(nv->name, "dev") == 0) { + lun_set_path(cl, nv->value); + continue; + } + lo = lun_option_new(cl, nv->name, nv->value); + if (lo == NULL) + log_warnx("unable to add CTL lun option %s " + "for CTL lun %ju for lun %d, target %s", + nv->name, (uintmax_t) lun->lun_id, + cl->l_lun, cl->l_target->t_iqn); + } + } + + return (conf); +} + +int +kernel_lun_add(struct lun *lun) +{ + struct lun_option *lo; + struct ctl_lun_req req; + char *tmp; + int error, i, num_options; + + 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; + + req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; + req.reqdata.create.device_type = T_DIRECT; + + if (lun->l_serial != NULL) { + strlcpy(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) { + strlcpy(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) { + lo = lun_option_find(lun, "file"); + if (lo != NULL) { + lun_option_set(lo, lun->l_path); + } else { + lo = lun_option_new(lun, "file", lun->l_path); + assert(lo != NULL); + } + } + + lo = lun_option_find(lun, "cfiscsi_target"); + if (lo != NULL) { + lun_option_set(lo, lun->l_target->t_iqn); + } else { + lo = lun_option_new(lun, "cfiscsi_target", + lun->l_target->t_iqn); + assert(lo != NULL); + } + + if (lun->l_target->t_alias != NULL) { + lo = lun_option_find(lun, "cfiscsi_target_alias"); + if (lo != NULL) { + lun_option_set(lo, lun->l_target->t_alias); + } else { + lo = lun_option_new(lun, "cfiscsi_target_alias", + lun->l_target->t_alias); + assert(lo != NULL); + } + } + + asprintf(&tmp, "%d", lun->l_lun); + if (tmp == NULL) + log_errx(1, "asprintf"); + lo = lun_option_find(lun, "cfiscsi_lun"); + if (lo != NULL) { + lun_option_set(lo, tmp); + free(tmp); + } else { + lo = lun_option_new(lun, "cfiscsi_lun", tmp); + free(tmp); + assert(lo != NULL); + } + + num_options = 0; + TAILQ_FOREACH(lo, &lun->l_options, lo_next) + num_options++; + + req.num_be_args = num_options; + if (num_options > 0) { + req.be_args = malloc(num_options * sizeof(*req.be_args)); + if (req.be_args == NULL) { + log_warn("error allocating %zd bytes", + num_options * sizeof(*req.be_args)); + return (1); + } + + i = 0; + TAILQ_FOREACH(lo, &lun->l_options, lo_next) { + /* + * +1 for the terminating '\0' + */ + req.be_args[i].namelen = strlen(lo->lo_name) + 1; + req.be_args[i].name = lo->lo_name; + req.be_args[i].vallen = strlen(lo->lo_value) + 1; + req.be_args[i].value = lo->lo_value; + req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD; + i++; + } + assert(i == num_options); + } + + error = ioctl(ctl_fd, CTL_LUN_REQ, &req); + free(req.be_args); + if (error != 0) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN creation request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN creation request status %d", + req.status); + return (1); + } + + lun_set_ctl_lun(lun, req.reqdata.create.req_lun_id); + + return (0); +} + +int +kernel_lun_resize(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_MODIFY; + + req.reqdata.modify.lun_id = lun->l_ctl_lun; + req.reqdata.modify.lun_size_bytes = lun->l_size; + + if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN modification request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN modification request 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); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN removal request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN removal request status %d", req.status); + return (1); + } + + return (0); +} + +void +kernel_handoff(struct 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)); + } + strlcpy(req.data.handoff.target_name, + conn->conn_target->t_iqn, sizeof(req.data.handoff.target_name)); + req.data.handoff.socket = conn->conn_socket; + req.data.handoff.portal_group_tag = + conn->conn_portal->p_portal_group->pg_tag; + if (conn->conn_header_digest == CONN_DIGEST_CRC32C) + req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; + if (conn->conn_data_digest == CONN_DIGEST_CRC32C) + req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; + req.data.handoff.cmdsn = conn->conn_cmdsn; + req.data.handoff.statsn = conn->conn_statsn; + req.data.handoff.max_recv_data_segment_length = + conn->conn_max_data_segment_length; + req.data.handoff.max_burst_length = conn->conn_max_burst_length; + req.data.handoff.immediate_data = conn->conn_immediate_data; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) + log_err(1, "error issuing CTL_ISCSI ioctl; " + "dropping connection"); + + if (req.status != CTL_ISCSI_OK) + log_errx(1, "error returned from CTL iSCSI handoff request: " + "%s; dropping connection", req.error_str); +} + +int +kernel_port_on(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + 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_off(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_DISABLE_PORT ioctl failed"); + return (-1); + } + + return (0); +} + +#ifdef ICL_KERNEL_PROXY +void +kernel_listen(struct addrinfo *ai, bool iser) +{ + 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; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) + log_warn("error issuing CTL_ISCSI_LISTEN ioctl"); +} + +int +kernel_accept(void) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_ACCEPT; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { + log_warn("error issuing CTL_ISCSI_LISTEN ioctl"); + return (0); + } + + return (req.data.accept.connection_id); +} + +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 ctl_iscsi req; + + pdu->pdu_data = malloc(MAX_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 = pdu->pdu_connection->conn_socket; + req.data.receive.bhs = pdu->pdu_bhs; + req.data.receive.data_segment_len = MAX_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) +{ + int error; + cap_rights_t rights; + const unsigned long cmds[] = { CTL_ISCSI }; + + cap_rights_init(&rights, CAP_IOCTL); + error = cap_rights_limit(ctl_fd, &rights); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_rights_limit"); + + error = cap_ioctls_limit(ctl_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"); +} + diff --git a/usr.sbin/ctld/keys.c b/usr.sbin/ctld/keys.c new file mode 100644 index 0000000..0ea4aa0 --- /dev/null +++ b/usr.sbin/ctld/keys.c @@ -0,0 +1,216 @@ +/*- + * 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 "ctld.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/ctld/log.c b/usr.sbin/ctld/log.c new file mode 100644 index 0000000..7b8ba71 --- /dev/null +++ b/usr.sbin/ctld/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 "ctld.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/ctld/login.c b/usr.sbin/ctld/login.c new file mode 100644 index 0000000..4ee7c97 --- /dev/null +++ b/usr.sbin/ctld/login.c @@ -0,0 +1,1051 @@ +/*- + * 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 <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <netinet/in.h> +#include <openssl/err.h> +#include <openssl/md5.h> +#include <openssl/rand.h> + +#include "ctld.h" +#include "iscsi_proto.h" + +static void login_send_error(struct pdu *request, + char class, char detail); + +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; +} + +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 (request->pdu_data_len == 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with empty data segment"); + } + if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) { + login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (initial == false && + ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { + login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with wrong ExpStatSN: " + "is %d, should be %d", 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) +{ + 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); + response = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_status_class = 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 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"); +} + +#define LOGIN_CHALLENGE_LEN 1024 + +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(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, const unsigned char id, + const void *challenge, const size_t challenge_len) +{ + struct pdu *response; + struct keys *response_keys; + char *chap_c, chap_i[4]; + + chap_c = login_bin2hex(challenge, challenge_len); + snprintf(chap_i, sizeof(chap_i), "%d", id); + + 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_c); + keys_save(response_keys, response); + keys_delete(response_keys); + pdu_send(response); +} + +static struct pdu * +login_receive_chap_r(struct connection *conn, + struct auth_group *ag, const unsigned char id, const void *challenge, + const size_t challenge_len, const struct auth **cap) +{ + struct pdu *request; + struct keys *request_keys; + const char *chap_n, *chap_r; + char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; + size_t response_bin_len; + const struct auth *auth; + int error; + + request = login_receive(conn, false); + request_keys = keys_new(); + keys_load(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 = login_hex2bin(chap_r, &response_bin, &response_bin_len); + 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); + login_compute_md5(id, auth->a_secret, challenge, + challenge_len, expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "CHAP authentication failed for user \"%s\"", + auth->a_user); + } + + keys_delete(request_keys); + free(response_bin); + + *cap = 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 iscsi_bhs_login_response *bhslr2; + const char *chap_i, *chap_c; + char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; + size_t challenge_len; + unsigned char id; + int error; + + response = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + + /* + * Actually, one more thing: mutual authentication. + */ + request_keys = keys_new(); + keys_load(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); + } + + id = strtoul(chap_i, NULL, 10); + error = login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed " + "CHAP_C"); + } + + log_debugx("performing mutual authentication as user \"%s\"", + auth->a_mutual_user); + login_compute_md5(id, auth->a_mutual_secret, challenge, + challenge_len, response_bin, sizeof(response_bin)); + + chap_r = login_bin2hex(response_bin, + sizeof(response_bin)); + 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(response_keys, response); + keys_delete(response_keys); + } else { + log_debugx("initiator did not request target authentication"); + } + + keys_delete(request_keys); + pdu_send(response); +} + +static void +login_chap(struct connection *conn, struct auth_group *ag) +{ + const struct auth *auth; + struct pdu *request; + char challenge_bin[LOGIN_CHALLENGE_LEN]; + unsigned char id; + int rv; + + /* + * Receive CHAP_A PDU. + */ + log_debugx("beginning CHAP authentication; waiting for CHAP_A"); + request = login_receive_chap_a(conn); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); + if (rv != 1) { + login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&id, sizeof(id)); + if (rv != 1) { + login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* + * Send the challenge. + */ + log_debugx("sending CHAP_C, binary challenge size is %zd bytes", + sizeof(challenge_bin)); + login_send_chap_c(request, id, challenge_bin, + sizeof(challenge_bin)); + pdu_delete(request); + + /* + * Receive CHAP_N/CHAP_R PDU and authenticate. + */ + log_debugx("waiting for CHAP_N/CHAP_R"); + request = login_receive_chap_r(conn, ag, id, challenge_bin, + sizeof(challenge_bin), &auth); + + /* + * Yay, authentication succeeded! + */ + log_debugx("authentication succeeded for user \"%s\"; " + "transitioning to Negotiation Phase", auth->a_user); + login_send_chap_success(request, auth); + pdu_delete(request); +} + +static void +login_negotiate_key(struct pdu *request, const char *name, + const char *value, bool skipped_security, struct keys *response_keys) +{ + int which, tmp; + struct connection *conn; + + conn = 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_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_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_immediate_data = true; + keys_add(response_keys, name, "Yes"); + } else { + conn->conn_immediate_data = false; + keys_add(response_keys, name, "No"); + } + } + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + } + if (tmp > MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping MaxDataSegmentLength from %d to %d", + tmp, MAX_DATA_SEGMENT_LENGTH); + tmp = MAX_DATA_SEGMENT_LENGTH; + } + conn->conn_max_data_segment_length = tmp; + keys_add_int(response_keys, name, 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 (tmp > MAX_BURST_LENGTH) { + log_debugx("capping MaxBurstLength from %d to %d", + tmp, MAX_BURST_LENGTH); + tmp = MAX_BURST_LENGTH; + } + conn->conn_max_burst_length = tmp; + keys_add(response_keys, name, value); + } 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 (tmp > MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping FirstBurstLength from %d to %d", + tmp, MAX_DATA_SEGMENT_LENGTH); + tmp = MAX_DATA_SEGMENT_LENGTH; + } + /* + * We don't pass the value to the kernel; it only enforces + * hardcoded limit anyway. + */ + 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 { + log_debugx("unknown key \"%s\"; responding " + "with NotUnderstood", name); + keys_add(response_keys, name, "NotUnderstood"); + } +} + +static void +login_negotiate(struct connection *conn, struct pdu *request) +{ + struct pdu *response; + struct iscsi_bhs_login_response *bhslr2; + struct keys *request_keys, *response_keys; + int i; + bool skipped_security; + + if (request == NULL) { + log_debugx("beginning parameter negotiation; " + "waiting for Login PDU"); + request = login_receive(conn, false); + skipped_security = false; + } else + skipped_security = true; + + request_keys = keys_new(); + keys_load(request_keys, request); + + response = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + 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(); + 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); + } + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature Phase"); + + keys_save(response_keys, response); + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); +} + +void +login(struct connection *conn) +{ + struct pdu *request, *response; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + struct keys *request_keys, *response_keys; + struct auth_group *ag; + const char *initiator_name, *initiator_alias, *session_type, + *target_name, *auth_method; + char *portal_group_tag; + int rv; + + /* + * Handle the initial Login Request - figure out required authentication + * method and either transition to the next phase, if no authentication + * is required, or call appropriate authentication code. + */ + log_debugx("beginning Login Phase; waiting for Login PDU"); + request = login_receive(conn, true); + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + if (bhslr->bhslr_tsih != 0) { + login_send_error(request, 0x02, 0x0a); + log_errx(1, "received Login PDU with non-zero TSIH"); + } + + /* + * XXX: Implement the C flag some day. + */ + request_keys = keys_new(); + keys_load(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) == 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); + /* + * XXX: This doesn't work (does nothing) because of Capsicum. + */ + setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name); + + 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_target = + target_find(conn->conn_portal->p_portal_group->pg_conf, + target_name); + if (conn->conn_target == NULL) { + login_send_error(request, 0x02, 0x03); + log_errx(1, "requested target \"%s\" not found", + target_name); + } + } + + /* + * At this point we know what kind of authentication we need. + */ + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + 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_iqn, + conn->conn_target->t_auth_group->ag_name); + } else { + log_debugx("initiator requests to connect " + "to target \"%s\"", conn->conn_target->t_iqn); + } + } else { + assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); + ag = conn->conn_portal->p_portal_group->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"); + } + } + + /* + * 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; + } + + if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) { + /* + * Initiator might want to to authenticate, + * but we don't need it. + */ + log_debugx("authentication not required; " + "transitioning to parameter negotiation"); + + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("initiator did not set the \"T\" flag; " + "transitioning anyway"); + + response = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + login_set_nsg(response, + BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + response_keys = keys_new(); + /* + * Required by Linux initiator. + */ + auth_method = keys_find(request_keys, "AuthMethod"); + if (auth_method != NULL && + login_list_contains(auth_method, "None")) + keys_add(response_keys, "AuthMethod", "None"); + + 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); + rv = asprintf(&portal_group_tag, "%d", + conn->conn_portal->p_portal_group->pg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + } + keys_save(response_keys, response); + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + login_negotiate(conn, NULL); + return; + } + + log_debugx("CHAP authentication required"); + + auth_method = keys_find(request_keys, "AuthMethod"); + if (auth_method == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without AuthMethod"); + } + /* + * XXX: This should be Reject, not just a login failure (5.3.2). + */ + if (login_list_contains(auth_method, "CHAP") == 0) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests unsupported AuthMethod \"%s\" " + "instead of \"CHAP\"", auth_method); + } + + response = login_new_response(request); + + response_keys = keys_new(); + keys_add(response_keys, "AuthMethod", "CHAP"); + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + conn->conn_portal->p_portal_group->pg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (conn->conn_target->t_alias != NULL) + keys_add(response_keys, + "TargetAlias", conn->conn_target->t_alias); + } + keys_save(response_keys, response); + + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + login_chap(conn, ag); + + login_negotiate(conn, NULL); +} diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y new file mode 100644 index 0000000..333e964 --- /dev/null +++ b/usr.sbin/ctld/parse.y @@ -0,0 +1,621 @@ +%{ +/*- + * 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/queue.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "ctld.h" + +extern FILE *yyin; +extern char *yytext; +extern int lineno; + +static struct conf *conf = NULL; +static struct auth_group *auth_group = NULL; +static struct portal_group *portal_group = NULL; +static struct target *target = NULL; +static struct lun *lun = NULL; + +extern void yyerror(const char *); +extern int yylex(void); +extern void yyrestart(FILE *); + +%} + +%token ALIAS AUTH_GROUP BACKEND BLOCKSIZE CHAP CHAP_MUTUAL CLOSING_BRACKET +%token DEBUG DEVICE_ID DISCOVERY_AUTH_GROUP LISTEN LISTEN_ISER LUN MAXPROC NUM +%token OPENING_BRACKET OPTION PATH PIDFILE PORTAL_GROUP SERIAL SIZE STR TARGET +%token TIMEOUT + +%union +{ + uint64_t num; + char *str; +} + +%token <num> NUM +%token <str> STR + +%% + +statements: + | + statements statement + ; + +statement: + debug_statement + | + timeout_statement + | + maxproc_statement + | + pidfile_statement + | + auth_group_definition + | + portal_group_definition + | + target_statement + ; + +debug_statement: DEBUG NUM + { + conf->conf_debug = $2; + } + ; + +timeout_statement: TIMEOUT NUM + { + conf->conf_timeout = $2; + } + ; + +maxproc_statement: MAXPROC NUM + { + conf->conf_maxproc = $2; + } + ; + +pidfile_statement: PIDFILE STR + { + if (conf->conf_pidfile_path != NULL) { + log_warnx("pidfile specified more than once"); + free($2); + return (1); + } + conf->conf_pidfile_path = $2; + } + ; + +auth_group_definition: AUTH_GROUP auth_group_name + OPENING_BRACKET auth_group_entries CLOSING_BRACKET + { + auth_group = NULL; + } + ; + +auth_group_name: STR + { + auth_group = auth_group_new(conf, $1); + free($1); + if (auth_group == NULL) + return (1); + } + ; + +auth_group_entries: + | + auth_group_entries auth_group_entry + ; + +auth_group_entry: + auth_group_chap + | + auth_group_chap_mutual + ; + +auth_group_chap: CHAP STR STR + { + const struct auth *ca; + + ca = auth_new_chap(auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR + { + const struct auth *ca; + + ca = auth_new_chap_mutual(auth_group, $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_definition: PORTAL_GROUP portal_group_name + OPENING_BRACKET portal_group_entries CLOSING_BRACKET + { + portal_group = NULL; + } + ; + +portal_group_name: STR + { + portal_group = portal_group_new(conf, $1); + free($1); + if (portal_group == NULL) + return (1); + } + ; + +portal_group_entries: + | + portal_group_entries portal_group_entry + ; + +portal_group_entry: + portal_group_discovery_auth_group + | + portal_group_listen + | + portal_group_listen_iser + ; + +portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR + { + if (portal_group->pg_discovery_auth_group != NULL) { + log_warnx("discovery-auth-group for portal-group " + "\"%s\" specified more than once", + portal_group->pg_name); + return (1); + } + portal_group->pg_discovery_auth_group = + auth_group_find(conf, $2); + if (portal_group->pg_discovery_auth_group == NULL) { + log_warnx("unknown discovery-auth-group \"%s\" " + "for portal-group \"%s\"", + $2, portal_group->pg_name); + return (1); + } + free($2); + } + ; + +portal_group_listen: LISTEN STR + { + int error; + + error = portal_group_add_listen(portal_group, $2, false); + free($2); + if (error != 0) + return (1); + } + ; + +portal_group_listen_iser: LISTEN_ISER STR + { + int error; + + error = portal_group_add_listen(portal_group, $2, true); + free($2); + if (error != 0) + return (1); + } + ; + +target_statement: TARGET target_iqn + OPENING_BRACKET target_entries CLOSING_BRACKET + { + target = NULL; + } + ; + +target_iqn: STR + { + target = target_new(conf, $1); + free($1); + if (target == NULL) + return (1); + } + ; + +target_entries: + | + target_entries target_entry + ; + +target_entry: + alias_statement + | + auth_group_statement + | + chap_statement + | + chap_mutual_statement + | + portal_group_statement + | + lun_statement + ; + +alias_statement: ALIAS STR + { + if (target->t_alias != NULL) { + log_warnx("alias for target \"%s\" " + "specified more than once", target->t_iqn); + return (1); + } + target->t_alias = $2; + } + ; + +auth_group_statement: AUTH_GROUP STR + { + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) + log_warnx("auth-group for target \"%s\" " + "specified more than once", target->t_iqn); + else + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + return (1); + } + target->t_auth_group = auth_group_find(conf, $2); + if (target->t_auth_group == NULL) { + log_warnx("unknown auth-group \"%s\" for target " + "\"%s\"", $2, target->t_iqn); + return (1); + } + free($2); + } + ; + +chap_statement: CHAP STR STR + { + const struct auth *ca; + + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + free($2); + free($3); + return (1); + } + } else { + target->t_auth_group = auth_group_new(conf, NULL); + if (target->t_auth_group == NULL) { + free($2); + free($3); + return (1); + } + target->t_auth_group->ag_target = target; + } + ca = auth_new_chap(target->t_auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +chap_mutual_statement: CHAP_MUTUAL STR STR STR STR + { + const struct auth *ca; + + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + free($2); + free($3); + free($4); + free($5); + return (1); + } + } else { + target->t_auth_group = auth_group_new(conf, NULL); + if (target->t_auth_group == NULL) { + free($2); + free($3); + free($4); + free($5); + return (1); + } + target->t_auth_group->ag_target = target; + } + ca = auth_new_chap_mutual(target->t_auth_group, + $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_statement: PORTAL_GROUP STR + { + if (target->t_portal_group != NULL) { + log_warnx("portal-group for target \"%s\" " + "specified more than once", target->t_iqn); + free($2); + return (1); + } + target->t_portal_group = portal_group_find(conf, $2); + if (target->t_portal_group == NULL) { + log_warnx("unknown portal-group \"%s\" for target " + "\"%s\"", $2, target->t_iqn); + free($2); + return (1); + } + free($2); + } + ; + +lun_statement: LUN lun_number + OPENING_BRACKET lun_statement_entries CLOSING_BRACKET + { + lun = NULL; + } + ; + +lun_number: NUM + { + lun = lun_new(target, $1); + if (lun == NULL) + return (1); + } + ; + +lun_statement_entries: + | + lun_statement_entries lun_statement_entry + ; + +lun_statement_entry: + backend_statement + | + blocksize_statement + | + device_id_statement + | + option_statement + | + path_statement + | + serial_statement + | + size_statement + ; + +backend_statement: BACKEND STR + { + if (lun->l_backend != NULL) { + log_warnx("backend for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_backend(lun, $2); + free($2); + } + ; + +blocksize_statement: BLOCKSIZE NUM + { + if (lun->l_blocksize != 0) { + log_warnx("blocksize for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + return (1); + } + lun_set_blocksize(lun, $2); + } + ; + +device_id_statement: DEVICE_ID STR + { + if (lun->l_device_id != NULL) { + log_warnx("device_id for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_device_id(lun, $2); + free($2); + } + ; + +option_statement: OPTION STR STR + { + struct lun_option *clo; + + clo = lun_option_new(lun, $2, $3); + free($2); + free($3); + if (clo == NULL) + return (1); + } + ; + +path_statement: PATH STR + { + if (lun->l_path != NULL) { + log_warnx("path for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_path(lun, $2); + free($2); + } + ; + +serial_statement: SERIAL STR + { + if (lun->l_serial != NULL) { + log_warnx("serial for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_serial(lun, $2); + free($2); + } + ; + +size_statement: SIZE NUM + { + if (lun->l_size != 0) { + log_warnx("size for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + return (1); + } + lun_set_size(lun, $2); + } + ; +%% + +void +yyerror(const char *str) +{ + + log_warnx("error in configuration file at line %d near '%s': %s", + lineno, yytext, str); +} + +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? + */ +} + +struct conf * +conf_new_from_file(const char *path) +{ + struct auth_group *ag; + struct portal_group *pg; + int error; + + log_debugx("obtaining configuration from %s", path); + + conf = conf_new(); + + ag = auth_group_new(conf, "no-authentication"); + ag->ag_type = AG_TYPE_NO_AUTHENTICATION; + + /* + * Here, the type doesn't really matter, as the group doesn't contain + * any entries and thus will always deny access. + */ + ag = auth_group_new(conf, "no-access"); + ag->ag_type = AG_TYPE_CHAP; + + pg = portal_group_new(conf, "default"); + portal_group_add_listen(pg, "0.0.0.0:3260", false); + portal_group_add_listen(pg, "[::]:3260", false); + + yyin = fopen(path, "r"); + if (yyin == NULL) { + log_warn("unable to open configuration file %s", path); + conf_delete(conf); + return (NULL); + } + check_perms(path); + lineno = 0; + yyrestart(yyin); + error = yyparse(); + auth_group = NULL; + portal_group = NULL; + target = NULL; + lun = NULL; + fclose(yyin); + if (error != 0) { + conf_delete(conf); + return (NULL); + } + + error = conf_verify(conf); + if (error != 0) { + conf_delete(conf); + return (NULL); + } + + return (conf); +} diff --git a/usr.sbin/ctld/pdu.c b/usr.sbin/ctld/pdu.c new file mode 100644 index 0000000..eb8921e --- /dev/null +++ b/usr.sbin/ctld/pdu.c @@ -0,0 +1,246 @@ +/*- + * 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 "ctld.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) +{ + size_t len; + + 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 <= MAX_DATA_SEGMENT_LENGTH); + pdu->pdu_data_len = len; +} + +void +pdu_send(struct pdu *pdu) +{ + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + kernel_send(pdu); +} + +#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 > MAX_DATA_SEGMENT_LENGTH) { + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %d", + 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); +} diff --git a/usr.sbin/ctld/token.l b/usr.sbin/ctld/token.l new file mode 100644 index 0000000..f968d97 --- /dev/null +++ b/usr.sbin/ctld/token.l @@ -0,0 +1,85 @@ +%{ +/*- + * 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 <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "ctld.h" +#include "y.tab.h" + +int lineno; + +#define YY_DECL int yylex(void) +extern int yylex(void); + +%} + +%option noinput +%option nounput + +%% +alias { return ALIAS; } +auth-group { return AUTH_GROUP; } +backend { return BACKEND; } +blocksize { return BLOCKSIZE; } +chap { return CHAP; } +chap-mutual { return CHAP_MUTUAL; } +debug { return DEBUG; } +device-id { return DEVICE_ID; } +discovery-auth-group { return DISCOVERY_AUTH_GROUP; } +listen { return LISTEN; } +listen-iser { return LISTEN_ISER; } +lun { return LUN; } +maxproc { return MAXPROC; } +option { return OPTION; } +path { return PATH; } +pidfile { return PIDFILE; } +portal-group { return PORTAL_GROUP; } +serial { return SERIAL; } +size { return SIZE; } +target { return TARGET; } +timeout { return TIMEOUT; } +[0-9]+[kKmMgGtTpPeE]? { if (expand_number(yytext, &yylval.num) == 0) + return NUM; + else + return STR; + } +\"[^"]+\" { yylval.str = strndup(yytext + 1, + strlen(yytext) - 2); return STR; } +[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; } +\{ { return OPENING_BRACKET; } +\} { return CLOSING_BRACKET; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%% 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); +} |