[Midnightbsd-cvs] src [10818] trunk/usr.sbin/ctld: add ctld

laffer1 at midnightbsd.org laffer1 at midnightbsd.org
Sun Jun 10 16:15:35 EDT 2018


Revision: 10818
          http://svnweb.midnightbsd.org/src/?rev=10818
Author:   laffer1
Date:     2018-06-10 16:15:35 -0400 (Sun, 10 Jun 2018)
Log Message:
-----------
add ctld

Added Paths:
-----------
    trunk/usr.sbin/ctld/
    trunk/usr.sbin/ctld/Makefile
    trunk/usr.sbin/ctld/chap.c
    trunk/usr.sbin/ctld/ctl.conf.5
    trunk/usr.sbin/ctld/ctld.8
    trunk/usr.sbin/ctld/ctld.c
    trunk/usr.sbin/ctld/ctld.h
    trunk/usr.sbin/ctld/discovery.c
    trunk/usr.sbin/ctld/isns.c
    trunk/usr.sbin/ctld/isns.h
    trunk/usr.sbin/ctld/kernel.c
    trunk/usr.sbin/ctld/keys.c
    trunk/usr.sbin/ctld/log.c
    trunk/usr.sbin/ctld/login.c
    trunk/usr.sbin/ctld/parse.y
    trunk/usr.sbin/ctld/pdu.c
    trunk/usr.sbin/ctld/token.l

Added: trunk/usr.sbin/ctld/Makefile
===================================================================
--- trunk/usr.sbin/ctld/Makefile	                        (rev 0)
+++ trunk/usr.sbin/ctld/Makefile	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,23 @@
+# $MidnightBSD$
+# $FreeBSD: stable/10/usr.sbin/ctld/Makefile 316235 2017-03-30 05:54:27Z ngie $
+
+PROG=		ctld
+SRCS=		chap.c ctld.c discovery.c isns.c kernel.c keys.c log.c
+SRCS+=		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=		${LIBBSDXML} ${LIBL} ${LIBMD} ${LIBSBUF} ${LIBUTIL}
+LDADD=		-lbsdxml -ll -lmd -lsbuf -lutil
+
+YFLAGS+=	-v
+CLEANFILES=	y.tab.c y.tab.h y.output
+
+WARNS?=		6
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include <bsd.prog.mk>


Property changes on: trunk/usr.sbin/ctld/Makefile
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/chap.c
===================================================================
--- trunk/usr.sbin/ctld/chap.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/chap.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,423 @@
+/* $MidnightBSD$ */
+/*-
+ * Copyright (c) 2014 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/chap.c 286219 2015-08-03 07:20:33Z trasz $");
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <md5.h>
+
+#include "ctld.h"
+
+static void
+chap_compute_md5(const char id, const char *secret,
+    const void *challenge, size_t challenge_len, void *response,
+    size_t response_len)
+{
+	MD5_CTX ctx;
+
+	assert(response_len == CHAP_DIGEST_LEN);
+
+	MD5Init(&ctx);
+	MD5Update(&ctx, &id, sizeof(id));
+	MD5Update(&ctx, secret, strlen(secret));
+	MD5Update(&ctx, challenge, challenge_len);
+	MD5Final(response, &ctx);
+}
+
+static int
+chap_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);
+	}
+}
+
+static int
+chap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
+{
+	char *bin;
+	int b64_len, bin_len;
+
+	b64_len = strlen(b64);
+	bin_len = (b64_len + 3) / 4 * 3;
+	bin = calloc(bin_len, 1);
+	if (bin == NULL)
+		log_err(1, "calloc");
+
+	bin_len = b64_pton(b64, bin, bin_len);
+	if (bin_len < 0) {
+		log_warnx("malformed base64 variable");
+		free(bin);
+		return (-1);
+	}
+	*binp = bin;
+	*bin_lenp = bin_len;
+	return (0);
+}
+
+/*
+ * XXX: Review this _carefully_.
+ */
+static int
+chap_hex2bin(const char *hex, void **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, "0b", strlen("0b")) == 0)
+		return (chap_b642bin(hex + 2, binp, bin_lenp));
+
+	if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
+		log_warnx("malformed variable, should start with \"0x\""
+		    " or \"0b\"");
+		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 = chap_hex2int(hex[i]);
+		if (nibble < 0) {
+			log_warnx("malformed variable, invalid char \"%c\"",
+			    hex[i]);
+			free(bin);
+			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);
+}
+
+#ifdef USE_BASE64
+static char *
+chap_bin2hex(const char *bin, size_t bin_len)
+{
+	unsigned char *b64, *tmp;
+	size_t b64_len;
+
+	b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
+	b64 = malloc(b64_len);
+	if (b64 == NULL)
+		log_err(1, "malloc");
+
+	tmp = b64;
+	tmp += sprintf(tmp, "0b");
+	b64_ntop(bin, bin_len, tmp, b64_len - 2);
+
+	return (b64);
+}
+#else
+static char *
+chap_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);
+}
+#endif /* !USE_BASE64 */
+
+struct chap *
+chap_new(void)
+{
+	struct chap *chap;
+
+	chap = calloc(sizeof(*chap), 1);
+	if (chap == NULL)
+		log_err(1, "calloc");
+
+	/*
+	 * Generate the challenge.
+	 */
+	arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
+	arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
+
+	return (chap);
+}
+
+char *
+chap_get_id(const struct chap *chap)
+{
+	char *chap_i;
+	int ret;
+
+	ret = asprintf(&chap_i, "%d", chap->chap_id);
+	if (ret < 0)
+		log_err(1, "asprintf");
+
+	return (chap_i);
+}
+
+char *
+chap_get_challenge(const struct chap *chap)
+{
+	char *chap_c;
+
+	chap_c = chap_bin2hex(chap->chap_challenge,
+	    sizeof(chap->chap_challenge));
+
+	return (chap_c);
+}
+
+static int
+chap_receive_bin(struct chap *chap, void *response, size_t response_len)
+{
+
+	if (response_len != sizeof(chap->chap_response)) {
+		log_debugx("got CHAP response with invalid length; "
+		    "got %zd, should be %zd",
+		    response_len, sizeof(chap->chap_response));
+		return (1);
+	}
+
+	memcpy(chap->chap_response, response, response_len);
+	return (0);
+}
+
+int
+chap_receive(struct chap *chap, const char *response)
+{
+	void *response_bin;
+	size_t response_bin_len;
+	int error;
+
+	error = chap_hex2bin(response, &response_bin, &response_bin_len);
+	if (error != 0) {
+		log_debugx("got incorrectly encoded CHAP response \"%s\"",
+		    response);
+		return (1);
+	}
+
+	error = chap_receive_bin(chap, response_bin, response_bin_len);
+	free(response_bin);
+
+	return (error);
+}
+
+int
+chap_authenticate(struct chap *chap, const char *secret)
+{
+	char expected_response[CHAP_DIGEST_LEN];
+
+	chap_compute_md5(chap->chap_id, secret,
+	    chap->chap_challenge, sizeof(chap->chap_challenge),
+	    expected_response, sizeof(expected_response));
+
+	if (memcmp(chap->chap_response,
+	    expected_response, sizeof(expected_response)) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+void
+chap_delete(struct chap *chap)
+{
+
+	free(chap);
+}
+
+struct rchap *
+rchap_new(const char *secret)
+{
+	struct rchap *rchap;
+
+	rchap = calloc(sizeof(*rchap), 1);
+	if (rchap == NULL)
+		log_err(1, "calloc");
+
+	rchap->rchap_secret = checked_strdup(secret);
+
+	return (rchap);
+}
+
+static void
+rchap_receive_bin(struct rchap *rchap, const unsigned char id,
+    const void *challenge, size_t challenge_len)
+{
+
+	rchap->rchap_id = id;
+	rchap->rchap_challenge = calloc(challenge_len, 1);
+	if (rchap->rchap_challenge == NULL)
+		log_err(1, "calloc");
+	memcpy(rchap->rchap_challenge, challenge, challenge_len);
+	rchap->rchap_challenge_len = challenge_len;
+}
+
+int
+rchap_receive(struct rchap *rchap, const char *id, const char *challenge)
+{
+	unsigned char id_bin;
+	void *challenge_bin;
+	size_t challenge_bin_len;
+
+	int error;
+
+	id_bin = strtoul(id, NULL, 10);
+
+	error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
+	if (error != 0) {
+		log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
+		    challenge);
+		return (1);
+	}
+
+	rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
+	free(challenge_bin);
+
+	return (0);
+}
+
+static void
+rchap_get_response_bin(struct rchap *rchap,
+    void **responsep, size_t *response_lenp)
+{
+	void *response_bin;
+	size_t response_bin_len = CHAP_DIGEST_LEN;
+
+	response_bin = calloc(response_bin_len, 1);
+	if (response_bin == NULL)
+		log_err(1, "calloc");
+
+	chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
+	    rchap->rchap_challenge, rchap->rchap_challenge_len,
+	    response_bin, response_bin_len);
+
+	*responsep = response_bin;
+	*response_lenp = response_bin_len;
+}
+
+char *
+rchap_get_response(struct rchap *rchap)
+{
+	void *response;
+	size_t response_len;
+	char *chap_r;
+
+	rchap_get_response_bin(rchap, &response, &response_len);
+	chap_r = chap_bin2hex(response, response_len);
+	free(response);
+
+	return (chap_r);
+}
+
+void
+rchap_delete(struct rchap *rchap)
+{
+
+	free(rchap->rchap_secret);
+	free(rchap->rchap_challenge);
+	free(rchap);
+}


Property changes on: trunk/usr.sbin/ctld/chap.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/ctl.conf.5
===================================================================
--- trunk/usr.sbin/ctld/ctl.conf.5	                        (rev 0)
+++ trunk/usr.sbin/ctld/ctl.conf.5	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,471 @@
+.\" $MidnightBSD$
+.\" Copyright (c) 2012 The FreeBSD Foundation
+.\" Copyright (c) 2015 Alexander Motin <mav at FreeBSD.org>
+.\" 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: stable/10/usr.sbin/ctld/ctl.conf.5 291387 2015-11-27 15:19:36Z mav $
+.\"
+.Dd November 9, 2015
+.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 #
+are interpreted as comments.
+The general syntax of the
+.Nm
+file is:
+.Bd -literal -offset indent
+.No pidfile Ar path
+
+.No auth-group Ar name No {
+.Dl chap Ar user Ar secret
+.Dl ...
+}
+
+.No portal-group Ar name No {
+.Dl listen Ar address
+.\".Dl listen-iser Ar address
+.Dl discovery-auth-group Ar name
+.Dl ...
+}
+
+.No lun Ar name No {
+.Dl path Ar path
+}
+
+.No target Ar name {
+.Dl auth-group Ar name
+.Dl portal-group Ar name Op Ar agname
+.Dl port Ar name
+.Dl lun Ar number Ar name
+.Dl lun Ar number No {
+.Dl 	path Ar path
+.Dl }
+.Dl ...
+}
+.Ed
+.Ss Global Context
+.Bl -tag -width indent
+.It Ic auth-group Ar name
+Create an
+.Sy auth-group
+configuration context,
+defining a new auth-group,
+which can then be assigned to any number of targets.
+.It Ic debug Ar level
+The debug verbosity level.
+The default is 0.
+.It Ic maxproc Ar number
+The limit for concurrently running child processes handling
+incoming connections.
+The default is 30.
+A setting of 0 disables the limit.
+.It Ic pidfile Ar path
+The path to the pidfile.
+The default is
+.Pa /var/run/ctld.pid .
+.It Ic portal-group Ar name
+Create a
+.Sy portal-group
+configuration context,
+defining a new portal-group,
+which can then be assigned to any number of targets.
+.It Ic lun Ar name
+Create a
+.Sy lun
+configuration context, defining a LUN to be exported by some target(s).
+.It Ic target Ar name
+Create a
+.Sy target
+configuration context, which can contain one or more
+.Sy lun
+contexts.
+.It Ic timeout Ar seconds
+The timeout for login sessions, after which the connection
+will be forcibly terminated.
+The default is 60.
+A setting of 0 disables the timeout.
+.It Ic isns-server Ar address
+An IPv4 or IPv6 address and optionally port of iSNS server to register on.
+.It Ic isns-period Ar seconds
+iSNS registration period.
+Registered Network Entity not updated during this period will be unregistered.
+The default is 900.
+.It Ic isns-timeout Ar seconds
+Timeout for iSNS requests.
+The default is 5.
+.El
+.Ss auth-group Context
+.Bl -tag -width indent
+.It Ic auth-type Ar type
+Sets the authentication type.
+Type can be either
+.Qq Ar none ,
+.Qq Ar deny ,
+.Qq Ar chap ,
+or
+.Qq Ar chap-mutual .
+In most cases it is not necessary to set the type using this clause;
+it is usually used to disable authentication for a given
+.Sy auth-group .
+.It Ic chap Ar user Ar secret
+A set of CHAP authentication credentials.
+Note that for any
+.Sy auth-group ,
+the configuration may only contain either
+.Sy chap
+or
+.Sy chap-mutual
+entries; it is an error to mix them.
+.It Ic chap-mutual Ar user Ar secret Ar mutualuser Ar mutualsecret
+A set of mutual CHAP authentication credentials.
+Note that for any
+.Sy auth-group ,
+the configuration may only contain either
+.Sy chap
+or
+.Sy chap-mutual
+entries; it is an error to mix them.
+.It Ic initiator-name Ar initiator-name
+An iSCSI initiator name.
+Only initiators with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+name.
+.It Ic initiator-portal Ar address Ns Op / Ns Ar prefixlen
+An iSCSI initiator portal: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only initiators with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+address.
+.El
+.Ss portal-group Context
+.Bl -tag -width indent
+.It Ic discovery-auth-group Ar name
+Assign a previously defined authentication group to the portal group,
+to be used for target discovery.
+By default, portal groups are assigned predefined
+.Sy auth-group
+.Qq Ar default ,
+which denies discovery.
+Another predefined
+.Sy auth-group ,
+.Qq Ar no-authentication ,
+may be used
+to permit discovery without authentication.
+.It Ic discovery-filter Ar filter
+Determines which targets are returned during discovery.
+Filter can be either
+.Qq Ar none ,
+.Qq Ar portal ,
+.Qq Ar portal-name ,
+or
+.Qq Ar portal-name-auth .
+When set to
+.Qq Ar none ,
+discovery will return all targets assigned to that portal group.
+When set to
+.Qq Ar portal ,
+discovery will not return targets that cannot be accessed by the
+initiator because of their
+.Sy initiator-portal .
+When set to
+.Qq Ar portal-name ,
+the check will include both
+.Sy initiator-portal
+and
+.Sy initiator-name .
+When set to
+.Qq Ar portal-name-auth ,
+the check will include
+.Sy initiator-portal ,
+.Sy initiator-name ,
+and authentication credentials.
+The target is returned if it does not require CHAP authentication,
+or if the CHAP user and secret used during discovery match those
+used by the target.
+Note that when using
+.Qq Ar portal-name-auth ,
+targets that require CHAP authentication will only be returned if
+.Sy discovery-auth-group
+requires CHAP.
+The default is
+.Qq Ar none .
+.It Ic listen Ar address
+An IPv4 or IPv6 address and port to listen on for incoming connections.
+.\".It Ic listen-iser Ar address
+.\"An IPv4 or IPv6 address and port to listen on for incoming connections
+.\"using iSER (iSCSI over RDMA) protocol.
+.It Ic option Ar name Ar value
+The CTL-specific port options passed to the kernel.
+.It Ic redirect Ar address
+IPv4 or IPv6 address to redirect initiators to.
+When configured, all initiators attempting to connect to portal
+belonging to this
+.Sy portal-group
+will get redirected using "Target moved temporarily" login response.
+Redirection happens before authentication and any
+.Sy initiator-name
+or
+.Sy initiator-portal
+checks are skipped.
+.It Ic tag Ar value
+Unique 16-bit tag value of this
+.Sy portal-group .
+If not specified, the value is generated automatically.
+.It Ic foreign
+Specifies that this
+.Sy portal-group
+is listened by some other host.
+This host will announce it on discovery stage, but won't listen.
+.El
+.Ss target Context
+.Bl -tag -width indent
+.It Ic alias Ar text
+Assign a human-readable description to the target.
+There is no default.
+.It Ic auth-group Ar name
+Assign a previously defined authentication group to the target.
+By default, targets that do not specify their own auth settings,
+using clauses such as
+.Sy chap
+or
+.Sy initiator-name ,
+are assigned
+predefined
+.Sy auth-group
+.Qq Ar default ,
+which denies all access.
+Another predefined
+.Sy auth-group ,
+.Qq Ar no-authentication ,
+may be used to permit access
+without authentication.
+Note that targets must only use one of
+.Sy auth-group , chap , No or Sy chap-mutual ;
+it is a configuration error to mix multiple types in one target.
+.It Ic auth-type Ar type
+Sets the authentication type.
+Type can be either
+.Qq Ar none ,
+.Qq Ar deny ,
+.Qq Ar chap ,
+or
+.Qq Ar chap-mutual .
+In most cases it is not necessary to set the type using this clause;
+it is usually used to disable authentication for a given
+.Sy target .
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.It Ic chap Ar user Ar secret
+A set of CHAP authentication credentials.
+Note that targets must only use one of
+.Sy auth-group , chap , No or Sy chap-mutual ;
+it is a configuration error to mix multiple types in one target.
+.It Ic chap-mutual Ar user Ar secret Ar mutualuser Ar mutualsecret
+A set of mutual CHAP authentication credentials.
+Note that targets must only use one of
+.Sy auth-group , chap , No or Sy chap-mutual ;
+it is a configuration error to mix multiple types in one target.
+.It Ic initiator-name Ar initiator-name
+An iSCSI initiator name.
+Only initiators with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+name.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.It Ic initiator-portal Ar address Ns Op / Ns Ar prefixlen
+An iSCSI initiator portal: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only initiators with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+address.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.It Ic portal-group Ar name Op Ar agname
+Assign a previously defined portal group to the target.
+The default portal group is
+.Qq Ar default ,
+which makes the target available
+on TCP port 3260 on all configured IPv4 and IPv6 addresses.
+Optional second argument specifies auth group name for connections
+to this specific portal group.
+If second argument is not specified, target auth group is used.
+.It Ic port Ar name
+.It Ic port Ar name/pp
+.It Ic port Ar name/pp/vp
+Assign specified CTL port (such as "isp0" or "isp2/1") to the target.
+On startup ctld configures LUN mapping and enables all assigned ports.
+Each port can be assigned to only one target.
+.It Ic redirect Aq Ar address
+IPv4 or IPv6 address to redirect initiators to.
+When configured, all initiators attempting to connect to this target
+will get redirected using "Target moved temporarily" login response.
+Redirection happens after successful authentication.
+.It Ic lun Ar number Ar name
+Export previously defined
+.Sy lun
+by the parent target.
+.It Ic lun Ar number
+Create a
+.Sy lun
+configuration context, defining a LUN exported by the parent target.
+.El
+.Ss lun Context
+.Bl -tag -width indent
+.It Ic backend Ar block No | Ar ramdisk
+The CTL backend to use for a given LUN.
+Valid choices are
+.Qq Ar block
+and
+.Qq Ar ramdisk ;
+block is used for LUNs backed
+by files or disk device nodes; ramdisk is a bitsink device, used mostly for
+testing.
+The default backend is block.
+.It Ic blocksize Ar size
+The blocksize visible to the initiator.
+The default blocksize is 512 for disks, and 2048 for CD/DVDs.
+.It Ic ctl-lun Ar lun_id
+Global numeric identifier to use for a given LUN inside CTL.
+By default CTL allocates those IDs dynamically, but explicit specification
+may be needed for consistency in HA configurations.
+.It Ic device-id Ar string
+The SCSI Device Identification string presented to the initiator.
+.It Ic device-type Ar type
+Specify the SCSI device type to use when creating the LUN.
+Currently CTL supports Direct Access (type 0), Processor (type 3)
+and CD/DVD (type 5) LUNs.
+.It Ic option Ar name Ar value
+The CTL-specific options passed to the kernel.
+All CTL-specific options are documented in the
+.Sx OPTIONS
+section of
+.Xr ctladm 8 .
+.It Ic path Ar path
+The path to the file, device node, or
+.Xr zfs 8
+volume used to back the LUN.
+For optimal performance, create the volume with the
+.Qq Ar volmode=dev
+property set.
+.It Ic serial Ar string
+The SCSI serial number presented to the initiator.
+.It Ic size Ar size
+The 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
+auth-group ag0 {
+	chap-mutual "user" "secret" "mutualuser" "mutualsecret"
+	chap-mutual "user2" "secret2" "mutualuser" "mutualsecret"
+}
+
+auth-group ag1 {
+	auth-type none
+	initiator-name "iqn.2012-06.com.example:initiatorhost1"
+	initiator-name "iqn.2012-06.com.example:initiatorhost2"
+	initiator-portal 192.168.1.1/24
+	initiator-portal [2001:db8::de:ef]
+}
+
+portal-group pg0 {
+	discovery-auth-group no-authentication
+	listen 0.0.0.0:3260
+	listen [::]:3260
+	listen [fe80::be:ef]:3261
+}
+
+target iqn.2012-06.com.example:target0 {
+	alias "Example target"
+	auth-group no-authentication
+	lun 0 {
+		path /dev/zvol/tank/example_0
+		blocksize 4096
+		size 4G
+	}
+}
+
+lun example_1 {
+	path /dev/zvol/tank/example_1
+	option naa 0x50015178f369f093
+}
+
+target iqn.2012-06.com.example:target1 {
+	auth-group ag0
+	portal-group pg0
+	lun 0 example_1
+	lun 1 {
+		path /dev/zvol/tank/example_2
+		option foo bar
+	}
+}
+
+target naa.50015178f369f092 {
+	port isp0
+	port isp1
+	lun 0 example_1
+}
+.Ed
+.Sh SEE ALSO
+.Xr ctl 4 ,
+.Xr ctladm 8 ,
+.Xr ctld 8 ,
+.Xr zfs 8
+.Sh AUTHORS
+The
+.Nm
+configuration file functionality for
+.Xr ctld 8
+was developed by
+.An Edward Tomasz Napierala Aq trasz at FreeBSD.org
+under sponsorship from the FreeBSD Foundation.


Property changes on: trunk/usr.sbin/ctld/ctl.conf.5
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/ctld.8
===================================================================
--- trunk/usr.sbin/ctld/ctld.8	                        (rev 0)
+++ trunk/usr.sbin/ctld/ctld.8	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,120 @@
+.\" $MidnightBSD$
+.\" 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: stable/10/usr.sbin/ctld/ctld.8 284671 2015-06-21 06:54:54Z trasz $
+.\"
+.Dd May 22, 2015
+.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
+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 necessary.
+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 unnecessary 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 - for example, 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 ,
+.Xr ctlstat 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq trasz at FreeBSD.org
+under sponsorship from the FreeBSD Foundation.


Property changes on: trunk/usr.sbin/ctld/ctld.8
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/ctld.c
===================================================================
--- trunk/usr.sbin/ctld/ctld.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/ctld.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,2618 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/ctld.c 317352 2017-04-24 06:33:08Z mav $");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.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"
+#include "isns.h"
+
+bool proxy_mode = false;
+
+static volatile bool sighup_received = false;
+static volatile bool sigterm_received = false;
+static volatile bool sigalrm_received = false;
+
+static int nchildren = 0;
+static uint16_t last_portal_group_tag = 0xff;
+
+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_luns);
+	TAILQ_INIT(&conf->conf_targets);
+	TAILQ_INIT(&conf->conf_auth_groups);
+	TAILQ_INIT(&conf->conf_ports);
+	TAILQ_INIT(&conf->conf_portal_groups);
+	TAILQ_INIT(&conf->conf_pports);
+	TAILQ_INIT(&conf->conf_isns);
+
+	conf->conf_isns_period = 900;
+	conf->conf_isns_timeout = 5;
+	conf->conf_debug = 0;
+	conf->conf_timeout = 60;
+	conf->conf_maxproc = 30;
+
+	return (conf);
+}
+
+void
+conf_delete(struct conf *conf)
+{
+	struct lun *lun, *ltmp;
+	struct target *targ, *tmp;
+	struct auth_group *ag, *cagtmp;
+	struct portal_group *pg, *cpgtmp;
+	struct pport *pp, *pptmp;
+	struct isns *is, *istmp;
+
+	assert(conf->conf_pidfh == NULL);
+
+	TAILQ_FOREACH_SAFE(lun, &conf->conf_luns, l_next, ltmp)
+		lun_delete(lun);
+	TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp)
+		target_delete(targ);
+	TAILQ_FOREACH_SAFE(ag, &conf->conf_auth_groups, ag_next, cagtmp)
+		auth_group_delete(ag);
+	TAILQ_FOREACH_SAFE(pg, &conf->conf_portal_groups, pg_next, cpgtmp)
+		portal_group_delete(pg);
+	TAILQ_FOREACH_SAFE(pp, &conf->conf_pports, pp_next, pptmp)
+		pport_delete(pp);
+	TAILQ_FOREACH_SAFE(is, &conf->conf_isns, i_next, istmp)
+		isns_delete(is);
+	assert(TAILQ_EMPTY(&conf->conf_ports));
+	free(conf->conf_pidfile_path);
+	free(conf);
+}
+
+static struct auth *
+auth_new(struct auth_group *ag)
+{
+	struct auth *auth;
+
+	auth = calloc(1, sizeof(*auth));
+	if (auth == NULL)
+		log_err(1, "calloc");
+	auth->a_auth_group = ag;
+	TAILQ_INSERT_TAIL(&ag->ag_auths, auth, a_next);
+	return (auth);
+}
+
+static void
+auth_delete(struct auth *auth)
+{
+	TAILQ_REMOVE(&auth->a_auth_group->ag_auths, auth, a_next);
+
+	free(auth->a_user);
+	free(auth->a_secret);
+	free(auth->a_mutual_user);
+	free(auth->a_mutual_secret);
+	free(auth);
+}
+
+const struct auth *
+auth_find(const struct auth_group *ag, const char *user)
+{
+	const struct auth *auth;
+
+	TAILQ_FOREACH(auth, &ag->ag_auths, a_next) {
+		if (strcmp(auth->a_user, user) == 0)
+			return (auth);
+	}
+
+	return (NULL);
+}
+
+static void
+auth_check_secret_length(struct auth *auth)
+{
+	size_t len;
+
+	len = strlen(auth->a_secret);
+	if (len > 16) {
+		if (auth->a_auth_group->ag_name != NULL)
+			log_warnx("secret for user \"%s\", auth-group \"%s\", "
+			    "is too long; it should be at most 16 characters "
+			    "long", auth->a_user, auth->a_auth_group->ag_name);
+		else
+			log_warnx("secret for user \"%s\", target \"%s\", "
+			    "is too long; it should be at most 16 characters "
+			    "long", auth->a_user,
+			    auth->a_auth_group->ag_target->t_name);
+	}
+	if (len < 12) {
+		if (auth->a_auth_group->ag_name != NULL)
+			log_warnx("secret for user \"%s\", auth-group \"%s\", "
+			    "is too short; it should be at least 12 characters "
+			    "long", auth->a_user,
+			    auth->a_auth_group->ag_name);
+		else
+			log_warnx("secret for user \"%s\", target \"%s\", "
+			    "is too short; it should be at least 12 characters "
+			    "long", auth->a_user,
+			    auth->a_auth_group->ag_target->t_name);
+	}
+
+	if (auth->a_mutual_secret != NULL) {
+		len = strlen(auth->a_mutual_secret);
+		if (len > 16) {
+			if (auth->a_auth_group->ag_name != NULL)
+				log_warnx("mutual secret for user \"%s\", "
+				    "auth-group \"%s\", is too long; it should "
+				    "be at most 16 characters long",
+				    auth->a_user, auth->a_auth_group->ag_name);
+			else
+				log_warnx("mutual secret for user \"%s\", "
+				    "target \"%s\", is too long; it should "
+				    "be at most 16 characters long",
+				    auth->a_user,
+				    auth->a_auth_group->ag_target->t_name);
+		}
+		if (len < 12) {
+			if (auth->a_auth_group->ag_name != NULL)
+				log_warnx("mutual secret for user \"%s\", "
+				    "auth-group \"%s\", is too short; it "
+				    "should be at least 12 characters long",
+				    auth->a_user, auth->a_auth_group->ag_name);
+			else
+				log_warnx("mutual secret for user \"%s\", "
+				    "target \"%s\", is too short; it should be "
+				    "at least 12 characters long",
+				    auth->a_user,
+				    auth->a_auth_group->ag_target->t_name);
+		}
+	}
+}
+
+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_name);
+		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_name);
+		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);
+}
+
+const struct auth_name *
+auth_name_new(struct auth_group *ag, const char *name)
+{
+	struct auth_name *an;
+
+	an = calloc(1, sizeof(*an));
+	if (an == NULL)
+		log_err(1, "calloc");
+	an->an_auth_group = ag;
+	an->an_initator_name = checked_strdup(name);
+	TAILQ_INSERT_TAIL(&ag->ag_names, an, an_next);
+	return (an);
+}
+
+static void
+auth_name_delete(struct auth_name *an)
+{
+	TAILQ_REMOVE(&an->an_auth_group->ag_names, an, an_next);
+
+	free(an->an_initator_name);
+	free(an);
+}
+
+bool
+auth_name_defined(const struct auth_group *ag)
+{
+	if (TAILQ_EMPTY(&ag->ag_names))
+		return (false);
+	return (true);
+}
+
+const struct auth_name *
+auth_name_find(const struct auth_group *ag, const char *name)
+{
+	const struct auth_name *auth_name;
+
+	TAILQ_FOREACH(auth_name, &ag->ag_names, an_next) {
+		if (strcmp(auth_name->an_initator_name, name) == 0)
+			return (auth_name);
+	}
+
+	return (NULL);
+}
+
+int
+auth_name_check(const struct auth_group *ag, const char *initiator_name)
+{
+	if (!auth_name_defined(ag))
+		return (0);
+
+	if (auth_name_find(ag, initiator_name) == NULL)
+		return (1);
+
+	return (0);
+}
+
+const struct auth_portal *
+auth_portal_new(struct auth_group *ag, const char *portal)
+{
+	struct auth_portal *ap;
+	char *net, *mask, *str, *tmp;
+	int len, dm, m;
+
+	ap = calloc(1, sizeof(*ap));
+	if (ap == NULL)
+		log_err(1, "calloc");
+	ap->ap_auth_group = ag;
+	ap->ap_initator_portal = checked_strdup(portal);
+	mask = str = checked_strdup(portal);
+	net = strsep(&mask, "/");
+	if (net[0] == '[')
+		net++;
+	len = strlen(net);
+	if (len == 0)
+		goto error;
+	if (net[len - 1] == ']')
+		net[len - 1] = 0;
+	if (strchr(net, ':') != NULL) {
+		struct sockaddr_in6 *sin6 =
+		    (struct sockaddr_in6 *)&ap->ap_sa;
+
+		sin6->sin6_len = sizeof(*sin6);
+		sin6->sin6_family = AF_INET6;
+		if (inet_pton(AF_INET6, net, &sin6->sin6_addr) <= 0)
+			goto error;
+		dm = 128;
+	} else {
+		struct sockaddr_in *sin =
+		    (struct sockaddr_in *)&ap->ap_sa;
+
+		sin->sin_len = sizeof(*sin);
+		sin->sin_family = AF_INET;
+		if (inet_pton(AF_INET, net, &sin->sin_addr) <= 0)
+			goto error;
+		dm = 32;
+	}
+	if (mask != NULL) {
+		m = strtol(mask, &tmp, 0);
+		if (m < 0 || m > dm || tmp[0] != 0)
+			goto error;
+	} else
+		m = dm;
+	ap->ap_mask = m;
+	free(str);
+	TAILQ_INSERT_TAIL(&ag->ag_portals, ap, ap_next);
+	return (ap);
+
+error:
+	free(str);
+	free(ap);
+	log_errx(1, "Incorrect initiator portal '%s'", portal);
+	return (NULL);
+}
+
+static void
+auth_portal_delete(struct auth_portal *ap)
+{
+	TAILQ_REMOVE(&ap->ap_auth_group->ag_portals, ap, ap_next);
+
+	free(ap->ap_initator_portal);
+	free(ap);
+}
+
+bool
+auth_portal_defined(const struct auth_group *ag)
+{
+	if (TAILQ_EMPTY(&ag->ag_portals))
+		return (false);
+	return (true);
+}
+
+const struct auth_portal *
+auth_portal_find(const struct auth_group *ag, const struct sockaddr_storage *ss)
+{
+	const struct auth_portal *ap;
+	const uint8_t *a, *b;
+	int i;
+	uint8_t bmask;
+
+	TAILQ_FOREACH(ap, &ag->ag_portals, ap_next) {
+		if (ap->ap_sa.ss_family != ss->ss_family)
+			continue;
+		if (ss->ss_family == AF_INET) {
+			a = (const uint8_t *)
+			    &((const struct sockaddr_in *)ss)->sin_addr;
+			b = (const uint8_t *)
+			    &((const struct sockaddr_in *)&ap->ap_sa)->sin_addr;
+		} else {
+			a = (const uint8_t *)
+			    &((const struct sockaddr_in6 *)ss)->sin6_addr;
+			b = (const uint8_t *)
+			    &((const struct sockaddr_in6 *)&ap->ap_sa)->sin6_addr;
+		}
+		for (i = 0; i < ap->ap_mask / 8; i++) {
+			if (a[i] != b[i])
+				goto next;
+		}
+		if (ap->ap_mask % 8) {
+			bmask = 0xff << (8 - (ap->ap_mask % 8));
+			if ((a[i] & bmask) != (b[i] & bmask))
+				goto next;
+		}
+		return (ap);
+next:
+		;
+	}
+
+	return (NULL);
+}
+
+int
+auth_portal_check(const struct auth_group *ag, const struct sockaddr_storage *sa)
+{
+
+	if (!auth_portal_defined(ag))
+		return (0);
+
+	if (auth_portal_find(ag, sa) == NULL)
+		return (1);
+
+	return (0);
+}
+
+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);
+	TAILQ_INIT(&ag->ag_names);
+	TAILQ_INIT(&ag->ag_portals);
+	ag->ag_conf = conf;
+	TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next);
+
+	return (ag);
+}
+
+void
+auth_group_delete(struct auth_group *ag)
+{
+	struct auth *auth, *auth_tmp;
+	struct auth_name *auth_name, *auth_name_tmp;
+	struct auth_portal *auth_portal, *auth_portal_tmp;
+
+	TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next);
+
+	TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, auth_tmp)
+		auth_delete(auth);
+	TAILQ_FOREACH_SAFE(auth_name, &ag->ag_names, an_next, auth_name_tmp)
+		auth_name_delete(auth_name);
+	TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_portals, ap_next,
+	    auth_portal_tmp)
+		auth_portal_delete(auth_portal);
+	free(ag->ag_name);
+	free(ag);
+}
+
+struct auth_group *
+auth_group_find(const struct conf *conf, const char *name)
+{
+	struct auth_group *ag;
+
+	TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+		if (ag->ag_name != NULL && strcmp(ag->ag_name, name) == 0)
+			return (ag);
+	}
+
+	return (NULL);
+}
+
+int
+auth_group_set_type(struct auth_group *ag, const char *str)
+{
+	int type;
+
+	if (strcmp(str, "none") == 0) {
+		type = AG_TYPE_NO_AUTHENTICATION;
+	} else if (strcmp(str, "deny") == 0) {
+		type = AG_TYPE_DENY;
+	} else if (strcmp(str, "chap") == 0) {
+		type = AG_TYPE_CHAP;
+	} else if (strcmp(str, "chap-mutual") == 0) {
+		type = AG_TYPE_CHAP_MUTUAL;
+	} else {
+		if (ag->ag_name != NULL)
+			log_warnx("invalid auth-type \"%s\" for auth-group "
+			    "\"%s\"", str, ag->ag_name);
+		else
+			log_warnx("invalid auth-type \"%s\" for target "
+			    "\"%s\"", str, ag->ag_target->t_name);
+		return (1);
+	}
+
+	if (ag->ag_type != AG_TYPE_UNKNOWN && ag->ag_type != type) {
+		if (ag->ag_name != NULL) {
+			log_warnx("cannot set auth-type to \"%s\" for "
+			    "auth-group \"%s\"; already has a different "
+			    "type", str, ag->ag_name);
+		} else {
+			log_warnx("cannot set auth-type to \"%s\" for target "
+			    "\"%s\"; already has a different type",
+			    str, ag->ag_target->t_name);
+		}
+		return (1);
+	}
+
+	ag->ag_type = type;
+
+	return (0);
+}
+
+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);
+	if (portal->p_ai != NULL)
+		freeaddrinfo(portal->p_ai);
+	free(portal->p_listen);
+	free(portal);
+}
+
+struct portal_group *
+portal_group_new(struct conf *conf, const char *name)
+{
+	struct portal_group *pg;
+
+	pg = portal_group_find(conf, name);
+	if (pg != NULL) {
+		log_warnx("duplicated portal-group \"%s\"", name);
+		return (NULL);
+	}
+
+	pg = calloc(1, sizeof(*pg));
+	if (pg == NULL)
+		log_err(1, "calloc");
+	pg->pg_name = checked_strdup(name);
+	TAILQ_INIT(&pg->pg_options);
+	TAILQ_INIT(&pg->pg_portals);
+	TAILQ_INIT(&pg->pg_ports);
+	pg->pg_conf = conf;
+	pg->pg_tag = 0;		/* Assigned later in conf_apply(). */
+	TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next);
+
+	return (pg);
+}
+
+void
+portal_group_delete(struct portal_group *pg)
+{
+	struct portal *portal, *tmp;
+	struct port *port, *tport;
+	struct option *o, *otmp;
+
+	TAILQ_FOREACH_SAFE(port, &pg->pg_ports, p_pgs, tport)
+		port_delete(port);
+	TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next);
+
+	TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp)
+		portal_delete(portal);
+	TAILQ_FOREACH_SAFE(o, &pg->pg_options, o_next, otmp)
+		option_delete(&pg->pg_options, o);
+	free(pg->pg_name);
+	free(pg->pg_redirection);
+	free(pg);
+}
+
+struct portal_group *
+portal_group_find(const struct conf *conf, const char *name)
+{
+	struct portal_group *pg;
+
+	TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+		if (strcmp(pg->pg_name, name) == 0)
+			return (pg);
+	}
+
+	return (NULL);
+}
+
+static int
+parse_addr_port(char *arg, const char *def_port, struct addrinfo **ai)
+{
+	struct addrinfo hints;
+	char *str, *addr, *ch;
+	const char *port;
+	int error, colons = 0;
+
+	str = arg = strdup(arg);
+	if (arg[0] == '[') {
+		/*
+		 * IPv6 address in square brackets, perhaps with port.
+		 */
+		arg++;
+		addr = strsep(&arg, "]");
+		if (arg == NULL) {
+			free(str);
+			return (1);
+		}
+		if (arg[0] == '\0') {
+			port = def_port;
+		} else if (arg[0] == ':') {
+			port = arg + 1;
+		} else {
+			free(str);
+			return (1);
+		}
+	} else {
+		/*
+		 * Either IPv6 address without brackets - and without
+		 * a port - or IPv4 address.  Just count the colons.
+		 */
+		for (ch = arg; *ch != '\0'; ch++) {
+			if (*ch == ':')
+				colons++;
+		}
+		if (colons > 1) {
+			addr = arg;
+			port = def_port;
+		} else {
+			addr = strsep(&arg, ":");
+			if (arg == NULL)
+				port = def_port;
+			else
+				port = arg;
+		}
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = PF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+	error = getaddrinfo(addr, port, &hints, ai);
+	free(str);
+	return ((error != 0) ? 1 : 0);
+}
+
+int
+portal_group_add_listen(struct portal_group *pg, const char *value, bool iser)
+{
+	struct portal *portal;
+
+	portal = portal_new(pg);
+	portal->p_listen = checked_strdup(value);
+	portal->p_iser = iser;
+
+	if (parse_addr_port(portal->p_listen, "3260", &portal->p_ai)) {
+		log_warnx("invalid listen address %s", portal->p_listen);
+		portal_delete(portal);
+		return (1);
+	}
+
+	/*
+	 * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+	 *	those into multiple portals.
+	 */
+
+	return (0);
+}
+
+int
+isns_new(struct conf *conf, const char *addr)
+{
+	struct isns *isns;
+
+	isns = calloc(1, sizeof(*isns));
+	if (isns == NULL)
+		log_err(1, "calloc");
+	isns->i_conf = conf;
+	TAILQ_INSERT_TAIL(&conf->conf_isns, isns, i_next);
+	isns->i_addr = checked_strdup(addr);
+
+	if (parse_addr_port(isns->i_addr, "3205", &isns->i_ai)) {
+		log_warnx("invalid iSNS address %s", isns->i_addr);
+		isns_delete(isns);
+		return (1);
+	}
+
+	/*
+	 * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+	 *	those into multiple servers.
+	 */
+
+	return (0);
+}
+
+void
+isns_delete(struct isns *isns)
+{
+
+	TAILQ_REMOVE(&isns->i_conf->conf_isns, isns, i_next);
+	free(isns->i_addr);
+	if (isns->i_ai != NULL)
+		freeaddrinfo(isns->i_ai);
+	free(isns);
+}
+
+static int
+isns_do_connect(struct isns *isns)
+{
+	int s;
+
+	s = socket(isns->i_ai->ai_family, isns->i_ai->ai_socktype,
+	    isns->i_ai->ai_protocol);
+	if (s < 0) {
+		log_warn("socket(2) failed for %s", isns->i_addr);
+		return (-1);
+	}
+	if (connect(s, isns->i_ai->ai_addr, isns->i_ai->ai_addrlen)) {
+		log_warn("connect(2) failed for %s", isns->i_addr);
+		close(s);
+		return (-1);
+	}
+	return(s);
+}
+
+static int
+isns_do_register(struct isns *isns, int s, const char *hostname)
+{
+	struct conf *conf = isns->i_conf;
+	struct target *target;
+	struct portal *portal;
+	struct portal_group *pg;
+	struct port *port;
+	struct isns_req *req;
+	int res = 0;
+	uint32_t error;
+
+	req = isns_req_create(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT);
+	isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	isns_req_add_delim(req);
+	isns_req_add_str(req, 1, hostname);
+	isns_req_add_32(req, 2, 2); /* 2 -- iSCSI */
+	isns_req_add_32(req, 6, conf->conf_isns_period);
+	TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+		if (pg->pg_unassigned)
+			continue;
+		TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+			isns_req_add_addr(req, 16, portal->p_ai);
+			isns_req_add_port(req, 17, portal->p_ai);
+		}
+	}
+	TAILQ_FOREACH(target, &conf->conf_targets, t_next) {
+		isns_req_add_str(req, 32, target->t_name);
+		isns_req_add_32(req, 33, 1); /* 1 -- Target*/
+		if (target->t_alias != NULL)
+			isns_req_add_str(req, 34, target->t_alias);
+		TAILQ_FOREACH(port, &target->t_ports, p_ts) {
+			if ((pg = port->p_portal_group) == NULL)
+				continue;
+			isns_req_add_32(req, 51, pg->pg_tag);
+			TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+				isns_req_add_addr(req, 49, portal->p_ai);
+				isns_req_add_port(req, 50, portal->p_ai);
+			}
+		}
+	}
+	res = isns_req_send(s, req);
+	if (res < 0) {
+		log_warn("send(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	res = isns_req_receive(s, req);
+	if (res < 0) {
+		log_warn("receive(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	error = isns_req_get_status(req);
+	if (error != 0) {
+		log_warnx("iSNS register error %d for %s", error, isns->i_addr);
+		res = -1;
+	}
+quit:
+	isns_req_free(req);
+	return (res);
+}
+
+static int
+isns_do_check(struct isns *isns, int s, const char *hostname)
+{
+	struct conf *conf = isns->i_conf;
+	struct isns_req *req;
+	int res = 0;
+	uint32_t error;
+
+	req = isns_req_create(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT);
+	isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	isns_req_add_str(req, 1, hostname);
+	isns_req_add_delim(req);
+	isns_req_add(req, 2, 0, NULL);
+	res = isns_req_send(s, req);
+	if (res < 0) {
+		log_warn("send(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	res = isns_req_receive(s, req);
+	if (res < 0) {
+		log_warn("receive(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	error = isns_req_get_status(req);
+	if (error != 0) {
+		log_warnx("iSNS check error %d for %s", error, isns->i_addr);
+		res = -1;
+	}
+quit:
+	isns_req_free(req);
+	return (res);
+}
+
+static int
+isns_do_deregister(struct isns *isns, int s, const char *hostname)
+{
+	struct conf *conf = isns->i_conf;
+	struct isns_req *req;
+	int res = 0;
+	uint32_t error;
+
+	req = isns_req_create(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT);
+	isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	isns_req_add_delim(req);
+	isns_req_add_str(req, 1, hostname);
+	res = isns_req_send(s, req);
+	if (res < 0) {
+		log_warn("send(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	res = isns_req_receive(s, req);
+	if (res < 0) {
+		log_warn("receive(2) failed for %s", isns->i_addr);
+		goto quit;
+	}
+	error = isns_req_get_status(req);
+	if (error != 0) {
+		log_warnx("iSNS deregister error %d for %s", error, isns->i_addr);
+		res = -1;
+	}
+quit:
+	isns_req_free(req);
+	return (res);
+}
+
+void
+isns_register(struct isns *isns, struct isns *oldisns)
+{
+	struct conf *conf = isns->i_conf;
+	int s;
+	char hostname[256];
+
+	if (TAILQ_EMPTY(&conf->conf_targets) ||
+	    TAILQ_EMPTY(&conf->conf_portal_groups))
+		return;
+	set_timeout(conf->conf_isns_timeout, false);
+	s = isns_do_connect(isns);
+	if (s < 0) {
+		set_timeout(0, false);
+		return;
+	}
+	gethostname(hostname, sizeof(hostname));
+
+	if (oldisns == NULL || TAILQ_EMPTY(&oldisns->i_conf->conf_targets))
+		oldisns = isns;
+	isns_do_deregister(oldisns, s, hostname);
+	isns_do_register(isns, s, hostname);
+	close(s);
+	set_timeout(0, false);
+}
+
+void
+isns_check(struct isns *isns)
+{
+	struct conf *conf = isns->i_conf;
+	int s, res;
+	char hostname[256];
+
+	if (TAILQ_EMPTY(&conf->conf_targets) ||
+	    TAILQ_EMPTY(&conf->conf_portal_groups))
+		return;
+	set_timeout(conf->conf_isns_timeout, false);
+	s = isns_do_connect(isns);
+	if (s < 0) {
+		set_timeout(0, false);
+		return;
+	}
+	gethostname(hostname, sizeof(hostname));
+
+	res = isns_do_check(isns, s, hostname);
+	if (res < 0) {
+		isns_do_deregister(isns, s, hostname);
+		isns_do_register(isns, s, hostname);
+	}
+	close(s);
+	set_timeout(0, false);
+}
+
+void
+isns_deregister(struct isns *isns)
+{
+	struct conf *conf = isns->i_conf;
+	int s;
+	char hostname[256];
+
+	if (TAILQ_EMPTY(&conf->conf_targets) ||
+	    TAILQ_EMPTY(&conf->conf_portal_groups))
+		return;
+	set_timeout(conf->conf_isns_timeout, false);
+	s = isns_do_connect(isns);
+	if (s < 0)
+		return;
+	gethostname(hostname, sizeof(hostname));
+
+	isns_do_deregister(isns, s, hostname);
+	close(s);
+	set_timeout(0, false);
+}
+
+int
+portal_group_set_filter(struct portal_group *pg, const char *str)
+{
+	int filter;
+
+	if (strcmp(str, "none") == 0) {
+		filter = PG_FILTER_NONE;
+	} else if (strcmp(str, "portal") == 0) {
+		filter = PG_FILTER_PORTAL;
+	} else if (strcmp(str, "portal-name") == 0) {
+		filter = PG_FILTER_PORTAL_NAME;
+	} else if (strcmp(str, "portal-name-auth") == 0) {
+		filter = PG_FILTER_PORTAL_NAME_AUTH;
+	} else {
+		log_warnx("invalid discovery-filter \"%s\" for portal-group "
+		    "\"%s\"; valid values are \"none\", \"portal\", "
+		    "\"portal-name\", and \"portal-name-auth\"",
+		    str, pg->pg_name);
+		return (1);
+	}
+
+	if (pg->pg_discovery_filter != PG_FILTER_UNKNOWN &&
+	    pg->pg_discovery_filter != filter) {
+		log_warnx("cannot set discovery-filter to \"%s\" for "
+		    "portal-group \"%s\"; already has a different "
+		    "value", str, pg->pg_name);
+		return (1);
+	}
+
+	pg->pg_discovery_filter = filter;
+
+	return (0);
+}
+
+int
+portal_group_set_redirection(struct portal_group *pg, const char *addr)
+{
+
+	if (pg->pg_redirection != NULL) {
+		log_warnx("cannot set redirection to \"%s\" for "
+		    "portal-group \"%s\"; already defined",
+		    addr, pg->pg_name);
+		return (1);
+	}
+
+	pg->pg_redirection = checked_strdup(addr);
+
+	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 pport *
+pport_new(struct conf *conf, const char *name, uint32_t ctl_port)
+{
+	struct pport *pp;
+
+	pp = calloc(1, sizeof(*pp));
+	if (pp == NULL)
+		log_err(1, "calloc");
+	pp->pp_conf = conf;
+	pp->pp_name = checked_strdup(name);
+	pp->pp_ctl_port = ctl_port;
+	TAILQ_INIT(&pp->pp_ports);
+	TAILQ_INSERT_TAIL(&conf->conf_pports, pp, pp_next);
+	return (pp);
+}
+
+struct pport *
+pport_find(const struct conf *conf, const char *name)
+{
+	struct pport *pp;
+
+	TAILQ_FOREACH(pp, &conf->conf_pports, pp_next) {
+		if (strcasecmp(pp->pp_name, name) == 0)
+			return (pp);
+	}
+	return (NULL);
+}
+
+struct pport *
+pport_copy(struct pport *pp, struct conf *conf)
+{
+	struct pport *ppnew;
+
+	ppnew = pport_new(conf, pp->pp_name, pp->pp_ctl_port);
+	return (ppnew);
+}
+
+void
+pport_delete(struct pport *pp)
+{
+	struct port *port, *tport;
+
+	TAILQ_FOREACH_SAFE(port, &pp->pp_ports, p_ts, tport)
+		port_delete(port);
+	TAILQ_REMOVE(&pp->pp_conf->conf_pports, pp, pp_next);
+	free(pp->pp_name);
+	free(pp);
+}
+
+struct port *
+port_new(struct conf *conf, struct target *target, struct portal_group *pg)
+{
+	struct port *port;
+	char *name;
+	int ret;
+
+	ret = asprintf(&name, "%s-%s", pg->pg_name, target->t_name);
+	if (ret <= 0)
+		log_err(1, "asprintf");
+	if (port_find(conf, name) != NULL) {
+		log_warnx("duplicate port \"%s\"", name);
+		free(name);
+		return (NULL);
+	}
+	port = calloc(1, sizeof(*port));
+	if (port == NULL)
+		log_err(1, "calloc");
+	port->p_conf = conf;
+	port->p_name = name;
+	TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
+	TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
+	port->p_target = target;
+	TAILQ_INSERT_TAIL(&pg->pg_ports, port, p_pgs);
+	port->p_portal_group = pg;
+	return (port);
+}
+
+struct port *
+port_new_pp(struct conf *conf, struct target *target, struct pport *pp)
+{
+	struct port *port;
+	char *name;
+	int ret;
+
+	ret = asprintf(&name, "%s-%s", pp->pp_name, target->t_name);
+	if (ret <= 0)
+		log_err(1, "asprintf");
+	if (port_find(conf, name) != NULL) {
+		log_warnx("duplicate port \"%s\"", name);
+		free(name);
+		return (NULL);
+	}
+	port = calloc(1, sizeof(*port));
+	if (port == NULL)
+		log_err(1, "calloc");
+	port->p_conf = conf;
+	port->p_name = name;
+	TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
+	TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
+	port->p_target = target;
+	TAILQ_INSERT_TAIL(&pp->pp_ports, port, p_pps);
+	port->p_pport = pp;
+	return (port);
+}
+
+struct port *
+port_find(const struct conf *conf, const char *name)
+{
+	struct port *port;
+
+	TAILQ_FOREACH(port, &conf->conf_ports, p_next) {
+		if (strcasecmp(port->p_name, name) == 0)
+			return (port);
+	}
+
+	return (NULL);
+}
+
+struct port *
+port_find_in_pg(const struct portal_group *pg, const char *target)
+{
+	struct port *port;
+
+	TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+		if (strcasecmp(port->p_target->t_name, target) == 0)
+			return (port);
+	}
+
+	return (NULL);
+}
+
+void
+port_delete(struct port *port)
+{
+
+	if (port->p_portal_group)
+		TAILQ_REMOVE(&port->p_portal_group->pg_ports, port, p_pgs);
+	if (port->p_pport)
+		TAILQ_REMOVE(&port->p_pport->pp_ports, port, p_pps);
+	if (port->p_target)
+		TAILQ_REMOVE(&port->p_target->t_ports, port, p_ts);
+	TAILQ_REMOVE(&port->p_conf->conf_ports, port, p_next);
+	free(port->p_name);
+	free(port);
+}
+
+int
+port_is_dummy(struct port *port)
+{
+
+	if (port->p_portal_group) {
+		if (port->p_portal_group->pg_foreign)
+			return (1);
+		if (TAILQ_EMPTY(&port->p_portal_group->pg_portals))
+			return (1);
+	}
+	return (0);
+}
+
+struct target *
+target_new(struct conf *conf, const char *name)
+{
+	struct target *targ;
+	int i, len;
+
+	targ = target_find(conf, name);
+	if (targ != NULL) {
+		log_warnx("duplicated target \"%s\"", name);
+		return (NULL);
+	}
+	if (valid_iscsi_name(name) == false) {
+		log_warnx("target name \"%s\" is invalid", name);
+		return (NULL);
+	}
+	targ = calloc(1, sizeof(*targ));
+	if (targ == NULL)
+		log_err(1, "calloc");
+	targ->t_name = checked_strdup(name);
+
+	/*
+	 * RFC 3722 requires us to normalize the name to lowercase.
+	 */
+	len = strlen(name);
+	for (i = 0; i < len; i++)
+		targ->t_name[i] = tolower(targ->t_name[i]);
+
+	targ->t_conf = conf;
+	TAILQ_INIT(&targ->t_ports);
+	TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
+
+	return (targ);
+}
+
+void
+target_delete(struct target *targ)
+{
+	struct port *port, *tport;
+
+	TAILQ_FOREACH_SAFE(port, &targ->t_ports, p_ts, tport)
+		port_delete(port);
+	TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
+
+	free(targ->t_name);
+	free(targ->t_redirection);
+	free(targ);
+}
+
+struct target *
+target_find(struct conf *conf, const char *name)
+{
+	struct target *targ;
+
+	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+		if (strcasecmp(targ->t_name, name) == 0)
+			return (targ);
+	}
+
+	return (NULL);
+}
+
+int
+target_set_redirection(struct target *target, const char *addr)
+{
+
+	if (target->t_redirection != NULL) {
+		log_warnx("cannot set redirection to \"%s\" for "
+		    "target \"%s\"; already defined",
+		    addr, target->t_name);
+		return (1);
+	}
+
+	target->t_redirection = checked_strdup(addr);
+
+	return (0);
+}
+
+struct lun *
+lun_new(struct conf *conf, const char *name)
+{
+	struct lun *lun;
+
+	lun = lun_find(conf, name);
+	if (lun != NULL) {
+		log_warnx("duplicated lun \"%s\"", name);
+		return (NULL);
+	}
+
+	lun = calloc(1, sizeof(*lun));
+	if (lun == NULL)
+		log_err(1, "calloc");
+	lun->l_conf = conf;
+	lun->l_name = checked_strdup(name);
+	TAILQ_INIT(&lun->l_options);
+	TAILQ_INSERT_TAIL(&conf->conf_luns, lun, l_next);
+	lun->l_ctl_lun = -1;
+
+	return (lun);
+}
+
+void
+lun_delete(struct lun *lun)
+{
+	struct target *targ;
+	struct option *o, *tmp;
+	int i;
+
+	TAILQ_FOREACH(targ, &lun->l_conf->conf_targets, t_next) {
+		for (i = 0; i < MAX_LUNS; i++) {
+			if (targ->t_luns[i] == lun)
+				targ->t_luns[i] = NULL;
+		}
+	}
+	TAILQ_REMOVE(&lun->l_conf->conf_luns, lun, l_next);
+
+	TAILQ_FOREACH_SAFE(o, &lun->l_options, o_next, tmp)
+		option_delete(&lun->l_options, o);
+	free(lun->l_name);
+	free(lun->l_backend);
+	free(lun->l_device_id);
+	free(lun->l_path);
+	free(lun->l_scsiname);
+	free(lun->l_serial);
+	free(lun);
+}
+
+struct lun *
+lun_find(const struct conf *conf, const char *name)
+{
+	struct lun *lun;
+
+	TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+		if (strcmp(lun->l_name, name) == 0)
+			return (lun);
+	}
+
+	return (NULL);
+}
+
+void
+lun_set_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_type(struct lun *lun, uint8_t value)
+{
+
+	lun->l_device_type = 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_scsiname(struct lun *lun, const char *value)
+{
+	free(lun->l_scsiname);
+	lun->l_scsiname = 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 option *
+option_new(struct options *options, const char *name, const char *value)
+{
+	struct option *o;
+
+	o = option_find(options, name);
+	if (o != NULL) {
+		log_warnx("duplicated option \"%s\"", name);
+		return (NULL);
+	}
+
+	o = calloc(1, sizeof(*o));
+	if (o == NULL)
+		log_err(1, "calloc");
+	o->o_name = checked_strdup(name);
+	o->o_value = checked_strdup(value);
+	TAILQ_INSERT_TAIL(options, o, o_next);
+
+	return (o);
+}
+
+void
+option_delete(struct options *options, struct option *o)
+{
+
+	TAILQ_REMOVE(options, o, o_next);
+	free(o->o_name);
+	free(o->o_value);
+	free(o);
+}
+
+struct option *
+option_find(const struct options *options, const char *name)
+{
+	struct option *o;
+
+	TAILQ_FOREACH(o, options, o_next) {
+		if (strcmp(o->o_name, name) == 0)
+			return (o);
+	}
+
+	return (NULL);
+}
+
+void
+option_set(struct option *o, const char *value)
+{
+
+	free(o->o_value);
+	o->o_value = checked_strdup(value);
+}
+
+static struct connection *
+connection_new(struct portal *portal, int fd, const char *host,
+    const struct sockaddr *client_sa)
+{
+	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);
+	memcpy(&conn->conn_initiator_sa, client_sa, client_sa->sa_len);
+
+	/*
+	 * 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 auth_name *auth_name;
+	struct auth_portal *auth_portal;
+	struct portal_group *pg;
+	struct portal *portal;
+	struct target *targ;
+	struct lun *lun;
+	struct option *o;
+
+	TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+		fprintf(stderr, "auth-group %s {\n", ag->ag_name);
+		TAILQ_FOREACH(auth, &ag->ag_auths, a_next)
+			fprintf(stderr, "\t chap-mutual %s %s %s %s\n",
+			    auth->a_user, auth->a_secret,
+			    auth->a_mutual_user, auth->a_mutual_secret);
+		TAILQ_FOREACH(auth_name, &ag->ag_names, an_next)
+			fprintf(stderr, "\t initiator-name %s\n",
+			    auth_name->an_initator_name);
+		TAILQ_FOREACH(auth_portal, &ag->ag_portals, an_next)
+			fprintf(stderr, "\t initiator-portal %s\n",
+			    auth_portal->an_initator_portal);
+		fprintf(stderr, "}\n");
+	}
+	TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+		fprintf(stderr, "portal-group %s {\n", pg->pg_name);
+		TAILQ_FOREACH(portal, &pg->pg_portals, p_next)
+			fprintf(stderr, "\t listen %s\n", portal->p_listen);
+		fprintf(stderr, "}\n");
+	}
+	TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+		fprintf(stderr, "\tlun %s {\n", lun->l_name);
+		fprintf(stderr, "\t\tpath %s\n", lun->l_path);
+		TAILQ_FOREACH(o, &lun->l_options, o_next)
+			fprintf(stderr, "\t\toption %s %s\n",
+			    lo->o_name, lo->o_value);
+		fprintf(stderr, "\t}\n");
+	}
+	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+		fprintf(stderr, "target %s {\n", targ->t_name);
+		if (targ->t_alias != NULL)
+			fprintf(stderr, "\t alias %s\n", targ->t_alias);
+		fprintf(stderr, "}\n");
+	}
+}
+#endif
+
+static int
+conf_verify_lun(struct lun *lun)
+{
+	const struct lun *lun2;
+
+	if (lun->l_backend == NULL)
+		lun_set_backend(lun, "block");
+	if (strcmp(lun->l_backend, "block") == 0) {
+		if (lun->l_path == NULL) {
+			log_warnx("missing path for lun \"%s\"",
+			    lun->l_name);
+			return (1);
+		}
+	} else if (strcmp(lun->l_backend, "ramdisk") == 0) {
+		if (lun->l_size == 0) {
+			log_warnx("missing size for ramdisk-backed lun \"%s\"",
+			    lun->l_name);
+			return (1);
+		}
+		if (lun->l_path != NULL) {
+			log_warnx("path must not be specified "
+			    "for ramdisk-backed lun \"%s\"",
+			    lun->l_name);
+			return (1);
+		}
+	}
+	if (lun->l_blocksize == 0) {
+		if (lun->l_device_type == 5)
+			lun_set_blocksize(lun, DEFAULT_CD_BLOCKSIZE);
+		else
+			lun_set_blocksize(lun, DEFAULT_BLOCKSIZE);
+	} else if (lun->l_blocksize < 0) {
+		log_warnx("invalid blocksize for lun \"%s\"; "
+		    "must be larger than 0", lun->l_name);
+		return (1);
+	}
+	if (lun->l_size != 0 && lun->l_size % lun->l_blocksize != 0) {
+		log_warnx("invalid size for lun \"%s\"; "
+		    "must be multiple of blocksize", lun->l_name);
+		return (1);
+	}
+	TAILQ_FOREACH(lun2, &lun->l_conf->conf_luns, l_next) {
+		if (lun == lun2)
+			continue;
+		if (lun->l_path != NULL && lun2->l_path != NULL &&
+		    strcmp(lun->l_path, lun2->l_path) == 0) {
+			log_debugx("WARNING: path \"%s\" duplicated "
+			    "between lun \"%s\", and "
+			    "lun \"%s\"", lun->l_path,
+			    lun->l_name, lun2->l_name);
+		}
+	}
+
+	return (0);
+}
+
+int
+conf_verify(struct conf *conf)
+{
+	struct auth_group *ag;
+	struct portal_group *pg;
+	struct port *port;
+	struct target *targ;
+	struct lun *lun;
+	bool found;
+	int error, i;
+
+	if (conf->conf_pidfile_path == NULL)
+		conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE);
+
+	TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+		error = conf_verify_lun(lun);
+		if (error != 0)
+			return (error);
+	}
+	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+		if (targ->t_auth_group == NULL) {
+			targ->t_auth_group = auth_group_find(conf,
+			    "default");
+			assert(targ->t_auth_group != NULL);
+		}
+		if (TAILQ_EMPTY(&targ->t_ports)) {
+			pg = portal_group_find(conf, "default");
+			assert(pg != NULL);
+			port_new(conf, targ, pg);
+		}
+		found = false;
+		for (i = 0; i < MAX_LUNS; i++) {
+			if (targ->t_luns[i] != NULL)
+				found = true;
+		}
+		if (!found && targ->t_redirection == NULL) {
+			log_warnx("no LUNs defined for target \"%s\"",
+			    targ->t_name);
+		}
+		if (found && targ->t_redirection != NULL) {
+			log_debugx("target \"%s\" contains luns, "
+			    " but configured for redirection",
+			    targ->t_name);
+		}
+	}
+	TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+		assert(pg->pg_name != NULL);
+		if (pg->pg_discovery_auth_group == NULL) {
+			pg->pg_discovery_auth_group =
+			    auth_group_find(conf, "default");
+			assert(pg->pg_discovery_auth_group != NULL);
+		}
+
+		if (pg->pg_discovery_filter == PG_FILTER_UNKNOWN)
+			pg->pg_discovery_filter = PG_FILTER_NONE;
+
+		if (pg->pg_redirection != NULL) {
+			if (!TAILQ_EMPTY(&pg->pg_ports)) {
+				log_debugx("portal-group \"%s\" assigned "
+				    "to target, but configured "
+				    "for redirection",
+				    pg->pg_name);
+			}
+			pg->pg_unassigned = false;
+		} else if (!TAILQ_EMPTY(&pg->pg_ports)) {
+			pg->pg_unassigned = false;
+		} else {
+			if (strcmp(pg->pg_name, "default") != 0)
+				log_warnx("portal-group \"%s\" not assigned "
+				    "to any target", pg->pg_name);
+			pg->pg_unassigned = true;
+		}
+	}
+	TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+		if (ag->ag_name == NULL)
+			assert(ag->ag_target != NULL);
+		else
+			assert(ag->ag_target == NULL);
+
+		found = false;
+		TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+			if (targ->t_auth_group == ag) {
+				found = true;
+				break;
+			}
+		}
+		TAILQ_FOREACH(port, &conf->conf_ports, p_next) {
+			if (port->p_auth_group == ag) {
+				found = true;
+				break;
+			}
+		}
+		TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+			if (pg->pg_discovery_auth_group == ag) {
+				found = true;
+				break;
+			}
+		}
+		if (!found && ag->ag_name != NULL &&
+		    strcmp(ag->ag_name, "default") != 0 &&
+		    strcmp(ag->ag_name, "no-authentication") != 0 &&
+		    strcmp(ag->ag_name, "no-access") != 0) {
+			log_warnx("auth-group \"%s\" not assigned "
+			    "to any target", ag->ag_name);
+		}
+	}
+
+	return (0);
+}
+
+static int
+conf_apply(struct conf *oldconf, struct conf *newconf)
+{
+	struct lun *oldlun, *newlun, *tmplun;
+	struct portal_group *oldpg, *newpg;
+	struct portal *oldp, *newp;
+	struct port *oldport, *newport, *tmpport;
+	struct isns *oldns, *newns;
+	pid_t otherpid;
+	int changed, cumulated_error = 0, error, sockbuf;
+	int one = 1;
+
+	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);
+		}
+	}
+
+	/*
+	 * Go through the new portal groups, assigning tags or preserving old.
+	 */
+	TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) {
+		if (newpg->pg_tag != 0)
+			continue;
+		oldpg = portal_group_find(oldconf, newpg->pg_name);
+		if (oldpg != NULL)
+			newpg->pg_tag = oldpg->pg_tag;
+		else
+			newpg->pg_tag = ++last_portal_group_tag;
+	}
+
+	/* Deregister on removed iSNS servers. */
+	TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) {
+		TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) {
+			if (strcmp(oldns->i_addr, newns->i_addr) == 0)
+				break;
+		}
+		if (newns == NULL)
+			isns_deregister(oldns);
+	}
+
+	/*
+	 * XXX: If target or lun removal fails, we should somehow "move"
+	 *      the old lun or target into newconf, so that subsequent
+	 *      conf_apply() would try to remove them again.  That would
+	 *      be somewhat hairy, though, and lun deletion failures don't
+	 *      really happen, so leave it as it is for now.
+	 */
+	/*
+	 * First, remove any ports present in the old configuration
+	 * and missing in the new one.
+	 */
+	TAILQ_FOREACH_SAFE(oldport, &oldconf->conf_ports, p_next, tmpport) {
+		if (port_is_dummy(oldport))
+			continue;
+		newport = port_find(newconf, oldport->p_name);
+		if (newport != NULL && !port_is_dummy(newport))
+			continue;
+		log_debugx("removing port \"%s\"", oldport->p_name);
+		error = kernel_port_remove(oldport);
+		if (error != 0) {
+			log_warnx("failed to remove port %s",
+			    oldport->p_name);
+			/*
+			 * XXX: Uncomment after fixing the root cause.
+			 *
+			 * cumulated_error++;
+			 */
+		}
+	}
+
+	/*
+	 * Second, remove any LUNs present in the old configuration
+	 * and missing in the new one.
+	 */
+	TAILQ_FOREACH_SAFE(oldlun, &oldconf->conf_luns, l_next, tmplun) {
+		newlun = lun_find(newconf, oldlun->l_name);
+		if (newlun == NULL) {
+			log_debugx("lun \"%s\", CTL lun %d "
+			    "not found in new configuration; "
+			    "removing", oldlun->l_name, oldlun->l_ctl_lun);
+			error = kernel_lun_remove(oldlun);
+			if (error != 0) {
+				log_warnx("failed to remove lun \"%s\", "
+				    "CTL lun %d",
+				    oldlun->l_name, oldlun->l_ctl_lun);
+				cumulated_error++;
+			}
+			continue;
+		}
+
+		/*
+		 * Also remove the LUNs changed by more than size.
+		 */
+		changed = 0;
+		assert(oldlun->l_backend != NULL);
+		assert(newlun->l_backend != NULL);
+		if (strcmp(newlun->l_backend, oldlun->l_backend) != 0) {
+			log_debugx("backend for lun \"%s\", "
+			    "CTL lun %d changed; removing",
+			    oldlun->l_name, oldlun->l_ctl_lun);
+			changed = 1;
+		}
+		if (oldlun->l_blocksize != newlun->l_blocksize) {
+			log_debugx("blocksize for lun \"%s\", "
+			    "CTL lun %d changed; removing",
+			    oldlun->l_name, oldlun->l_ctl_lun);
+			changed = 1;
+		}
+		if (newlun->l_device_id != NULL &&
+		    (oldlun->l_device_id == NULL ||
+		     strcmp(oldlun->l_device_id, newlun->l_device_id) !=
+		     0)) {
+			log_debugx("device-id for lun \"%s\", "
+			    "CTL lun %d changed; removing",
+			    oldlun->l_name, oldlun->l_ctl_lun);
+			changed = 1;
+		}
+		if (newlun->l_path != NULL &&
+		    (oldlun->l_path == NULL ||
+		     strcmp(oldlun->l_path, newlun->l_path) != 0)) {
+			log_debugx("path for lun \"%s\", "
+			    "CTL lun %d, changed; removing",
+			    oldlun->l_name, oldlun->l_ctl_lun);
+			changed = 1;
+		}
+		if (newlun->l_serial != NULL &&
+		    (oldlun->l_serial == NULL ||
+		     strcmp(oldlun->l_serial, newlun->l_serial) != 0)) {
+			log_debugx("serial for lun \"%s\", "
+			    "CTL lun %d changed; removing",
+			    oldlun->l_name, oldlun->l_ctl_lun);
+			changed = 1;
+		}
+		if (changed) {
+			error = kernel_lun_remove(oldlun);
+			if (error != 0) {
+				log_warnx("failed to remove lun \"%s\", "
+				    "CTL lun %d",
+				    oldlun->l_name, oldlun->l_ctl_lun);
+				cumulated_error++;
+			}
+			lun_delete(oldlun);
+			continue;
+		}
+
+		lun_set_ctl_lun(newlun, oldlun->l_ctl_lun);
+	}
+
+	TAILQ_FOREACH_SAFE(newlun, &newconf->conf_luns, l_next, tmplun) {
+		oldlun = lun_find(oldconf, newlun->l_name);
+		if (oldlun != NULL) {
+			log_debugx("modifying lun \"%s\", CTL lun %d",
+			    newlun->l_name, newlun->l_ctl_lun);
+			error = kernel_lun_modify(newlun);
+			if (error != 0) {
+				log_warnx("failed to "
+				    "modify lun \"%s\", CTL lun %d",
+				    newlun->l_name, newlun->l_ctl_lun);
+				cumulated_error++;
+			}
+			continue;
+		}
+		log_debugx("adding lun \"%s\"", newlun->l_name);
+		error = kernel_lun_add(newlun);
+		if (error != 0) {
+			log_warnx("failed to add lun \"%s\"", newlun->l_name);
+			lun_delete(newlun);
+			cumulated_error++;
+		}
+	}
+
+	/*
+	 * Now add new ports or modify existing ones.
+	 */
+	TAILQ_FOREACH(newport, &newconf->conf_ports, p_next) {
+		if (port_is_dummy(newport))
+			continue;
+		oldport = port_find(oldconf, newport->p_name);
+
+		if (oldport == NULL || port_is_dummy(oldport)) {
+			log_debugx("adding port \"%s\"", newport->p_name);
+			error = kernel_port_add(newport);
+		} else {
+			log_debugx("updating port \"%s\"", newport->p_name);
+			newport->p_ctl_port = oldport->p_ctl_port;
+			error = kernel_port_update(newport, oldport);
+		}
+		if (error != 0) {
+			log_warnx("failed to %s port %s",
+			    (oldport == NULL) ? "add" : "update",
+			    newport->p_name);
+			/*
+			 * XXX: Uncomment after fixing the root cause.
+			 *
+			 * cumulated_error++;
+			 */
+		}
+	}
+
+	/*
+	 * Go through the new portals, opening the sockets as necessary.
+	 */
+	TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) {
+		if (newpg->pg_foreign)
+			continue;
+		if (newpg->pg_unassigned) {
+			log_debugx("not listening on portal-group \"%s\", "
+			    "not assigned to any target",
+			    newpg->pg_name);
+			continue;
+		}
+		TAILQ_FOREACH(newp, &newpg->pg_portals, p_next) {
+			/*
+			 * Try to find already open portal and reuse
+			 * the listening socket.  We don't care about
+			 * what portal or portal group that was, what
+			 * matters is the listening address.
+			 */
+			TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups,
+			    pg_next) {
+				TAILQ_FOREACH(oldp, &oldpg->pg_portals,
+				    p_next) {
+					if (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
+			if (proxy_mode) {
+				newpg->pg_conf->conf_portal_id++;
+				newp->p_id = newpg->pg_conf->conf_portal_id;
+				log_debugx("listening on %s, portal-group "
+				    "\"%s\", portal id %d, using ICL proxy",
+				    newp->p_listen, newpg->pg_name, newp->p_id);
+				kernel_listen(newp->p_ai, newp->p_iser,
+				    newp->p_id);
+				continue;
+			}
+#endif
+			assert(proxy_mode == false);
+			assert(newp->p_iser == false);
+
+			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;
+			}
+			sockbuf = SOCKBUF_SIZE;
+			if (setsockopt(newp->p_socket, SOL_SOCKET, SO_RCVBUF,
+			    &sockbuf, sizeof(sockbuf)) == -1)
+				log_warn("setsockopt(SO_RCVBUF) failed "
+				    "for %s", newp->p_listen);
+			sockbuf = SOCKBUF_SIZE;
+			if (setsockopt(newp->p_socket, SOL_SOCKET, SO_SNDBUF,
+			    &sockbuf, sizeof(sockbuf)) == -1)
+				log_warn("setsockopt(SO_SNDBUF) failed "
+				    "for %s", newp->p_listen);
+			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;
+			}
+		}
+	}
+
+	/*
+	 * Go through the no longer used sockets, closing them.
+	 */
+	TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) {
+		TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) {
+			if (oldp->p_socket <= 0)
+				continue;
+			log_debugx("closing socket for %s, portal-group \"%s\"",
+			    oldp->p_listen, oldpg->pg_name);
+			close(oldp->p_socket);
+			oldp->p_socket = 0;
+		}
+	}
+
+	/* (Re-)Register on remaining/new iSNS servers. */
+	TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) {
+		TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) {
+			if (strcmp(oldns->i_addr, newns->i_addr) == 0)
+				break;
+		}
+		isns_register(newns, oldns);
+	}
+
+	/* Schedule iSNS update */
+	if (!TAILQ_EMPTY(&newconf->conf_isns))
+		set_timeout((newconf->conf_isns_period + 2) / 3, false);
+
+	return (cumulated_error);
+}
+
+bool
+timed_out(void)
+{
+
+	return (sigalrm_received);
+}
+
+static void
+sigalrm_handler_fatal(int dummy __unused)
+{
+	/*
+	 * It would be easiest to just log an error and exit.  We can't
+	 * do this, though, because log_errx() is not signal safe, since
+	 * it calls syslog(3).  Instead, set a flag checked by pdu_send()
+	 * and pdu_receive(), to call log_errx() there.  Should they fail
+	 * to notice, we'll exit here one second later.
+	 */
+	if (sigalrm_received) {
+		/*
+		 * Oh well.  Just give up and quit.
+		 */
+		_exit(2);
+	}
+
+	sigalrm_received = true;
+}
+
+static void
+sigalrm_handler(int dummy __unused)
+{
+
+	sigalrm_received = true;
+}
+
+void
+set_timeout(int timeout, int fatal)
+{
+	struct sigaction sa;
+	struct itimerval itv;
+	int error;
+
+	if (timeout <= 0) {
+		log_debugx("session timeout disabled");
+		bzero(&itv, sizeof(itv));
+		error = setitimer(ITIMER_REAL, &itv, NULL);
+		if (error != 0)
+			log_err(1, "setitimer");
+		sigalrm_received = false;
+		return;
+	}
+
+	sigalrm_received = false;
+	bzero(&sa, sizeof(sa));
+	if (fatal)
+		sa.sa_handler = sigalrm_handler_fatal;
+	else
+		sa.sa_handler = sigalrm_handler;
+	sigfillset(&sa.sa_mask);
+	error = sigaction(SIGALRM, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+
+	/*
+	 * First SIGALRM will arive after conf_timeout seconds.
+	 * If we do nothing, another one will arrive a second later.
+	 */
+	log_debugx("setting session timeout to %d seconds", timeout);
+	bzero(&itv, sizeof(itv));
+	itv.it_interval.tv_sec = 1;
+	itv.it_value.tv_sec = timeout;
+	error = setitimer(ITIMER_REAL, &itv, NULL);
+	if (error != 0)
+		log_err(1, "setitimer");
+}
+
+static int
+wait_for_children(bool block)
+{
+	pid_t pid;
+	int status;
+	int num = 0;
+
+	for (;;) {
+		/*
+		 * If "block" is true, wait for at least one process.
+		 */
+		if (block && num == 0)
+			pid = wait4(-1, &status, 0, NULL);
+		else
+			pid = wait4(-1, &status, WNOHANG, NULL);
+		if (pid <= 0)
+			break;
+		if (WIFSIGNALED(status)) {
+			log_warnx("child process %d terminated with signal %d",
+			    pid, WTERMSIG(status));
+		} else if (WEXITSTATUS(status) != 0) {
+			log_warnx("child process %d terminated with exit status %d",
+			    pid, WEXITSTATUS(status));
+		} else {
+			log_debugx("child process %d terminated gracefully", pid);
+		}
+		num++;
+	}
+
+	return (num);
+}
+
+static void
+handle_connection(struct portal *portal, int fd,
+    const struct sockaddr *client_sa, bool dont_fork)
+{
+	struct connection *conn;
+	int error;
+	pid_t pid;
+	char host[NI_MAXHOST + 1];
+	struct conf *conf;
+
+	conf = portal->p_portal_group->pg_conf;
+
+	if (dont_fork) {
+		log_debugx("incoming connection; not forking due to -d flag");
+	} else {
+		nchildren -= wait_for_children(false);
+		assert(nchildren >= 0);
+
+		while (conf->conf_maxproc > 0 && nchildren >= conf->conf_maxproc) {
+			log_debugx("maxproc limit of %d child processes hit; "
+			    "waiting for child process to exit", conf->conf_maxproc);
+			nchildren -= wait_for_children(true);
+			assert(nchildren >= 0);
+		}
+		log_debugx("incoming connection; forking child process #%d",
+		    nchildren);
+		nchildren++;
+		pid = fork();
+		if (pid < 0)
+			log_err(1, "fork");
+		if (pid > 0) {
+			close(fd);
+			return;
+		}
+	}
+	pidfile_close(conf->conf_pidfh);
+
+	error = getnameinfo(client_sa, client_sa->sa_len,
+	    host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+	if (error != 0)
+		log_errx(1, "getnameinfo: %s", gai_strerror(error));
+
+	log_debugx("accepted connection from %s; portal group \"%s\"",
+	    host, portal->p_portal_group->pg_name);
+	log_set_peer_addr(host);
+	setproctitle("%s", host);
+
+	conn = connection_new(portal, fd, host, client_sa);
+	set_timeout(conf->conf_timeout, true);
+	kernel_capsicate();
+	login(conn);
+	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+		kernel_handoff(conn);
+		log_debugx("connection handed off to the kernel");
+	} else {
+		assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
+		discovery(conn);
+	}
+	log_debugx("nothing more to do; exiting");
+	exit(0);
+}
+
+static 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);
+}
+
+static void
+main_loop(struct conf *conf, bool dont_fork)
+{
+	struct portal_group *pg;
+	struct portal *portal;
+	struct sockaddr_storage client_sa;
+	socklen_t client_salen;
+#ifdef ICL_KERNEL_PROXY
+	int connection_id;
+	int portal_id;
+#endif
+	fd_set fdset;
+	int error, nfds, client_fd;
+
+	pidfile_write(conf->conf_pidfh);
+
+	for (;;) {
+		if (sighup_received || sigterm_received || timed_out())
+			return;
+
+#ifdef ICL_KERNEL_PROXY
+		if (proxy_mode) {
+			client_salen = sizeof(client_sa);
+			kernel_accept(&connection_id, &portal_id,
+			    (struct sockaddr *)&client_sa, &client_salen);
+			assert(client_salen >= client_sa.ss_len);
+
+			log_debugx("incoming connection, id %d, portal id %d",
+			    connection_id, portal_id);
+			TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+				TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+					if (portal->p_id == portal_id) {
+						goto found;
+					}
+				}
+			}
+
+			log_errx(1, "kernel returned invalid portal_id %d",
+			    portal_id);
+
+found:
+			handle_connection(portal, connection_id,
+			    (struct sockaddr *)&client_sa, dont_fork);
+		} else {
+#endif
+			assert(proxy_mode == false);
+
+			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_salen = sizeof(client_sa);
+					client_fd = accept(portal->p_socket,
+					    (struct sockaddr *)&client_sa,
+					    &client_salen);
+					if (client_fd < 0) {
+						if (errno == ECONNABORTED)
+							continue;
+						log_err(1, "accept");
+					}
+					assert(client_salen >= client_sa.ss_len);
+
+					handle_connection(portal, client_fd,
+					    (struct sockaddr *)&client_sa,
+					    dont_fork);
+					break;
+				}
+			}
+#ifdef ICL_KERNEL_PROXY
+		}
+#endif
+	}
+}
+
+static void
+sighup_handler(int dummy __unused)
+{
+
+	sighup_received = true;
+}
+
+static void
+sigterm_handler(int dummy __unused)
+{
+
+	sigterm_received = true;
+}
+
+static void
+sigchld_handler(int dummy __unused)
+{
+
+	/*
+	 * The only purpose of this handler is to make SIGCHLD
+	 * interrupt the ISCSIDWAIT ioctl(2), so we can call
+	 * wait_for_children().
+	 */
+}
+
+static void
+register_signals(void)
+{
+	struct sigaction sa;
+	int error;
+
+	bzero(&sa, sizeof(sa));
+	sa.sa_handler = sighup_handler;
+	sigfillset(&sa.sa_mask);
+	error = sigaction(SIGHUP, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+
+	sa.sa_handler = sigterm_handler;
+	error = sigaction(SIGTERM, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+
+	sa.sa_handler = sigterm_handler;
+	error = sigaction(SIGINT, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+
+	sa.sa_handler = sigchld_handler;
+	error = sigaction(SIGCHLD, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+}
+
+int
+main(int argc, char **argv)
+{
+	struct conf *oldconf, *newconf, *tmpconf;
+	struct isns *newns;
+	const char *config_path = DEFAULT_CONFIG_PATH;
+	int debug = 0, ch, error;
+	bool dont_daemonize = false;
+
+	while ((ch = getopt(argc, argv, "df:R")) != -1) {
+		switch (ch) {
+		case 'd':
+			dont_daemonize = true;
+			debug++;
+			break;
+		case 'f':
+			config_path = optarg;
+			break;
+		case 'R':
+#ifndef ICL_KERNEL_PROXY
+			log_errx(1, "ctld(8) compiled without ICL_KERNEL_PROXY "
+			    "does not support iSER protocol");
+#endif
+			proxy_mode = true;
+			break;
+		case '?':
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	if (argc != 0)
+		usage();
+
+	log_init(debug);
+	kernel_init();
+
+	oldconf = conf_new_from_kernel();
+	newconf = conf_new_from_file(config_path, oldconf);
+	if (newconf == NULL)
+		log_errx(1, "configuration error; exiting");
+	if (debug > 0) {
+		oldconf->conf_debug = debug;
+		newconf->conf_debug = debug;
+	}
+
+	error = conf_apply(oldconf, newconf);
+	if (error != 0)
+		log_errx(1, "failed to apply configuration; exiting");
+
+	conf_delete(oldconf);
+	oldconf = NULL;
+
+	register_signals();
+
+	if (dont_daemonize == false) {
+		log_debugx("daemonizing");
+		if (daemon(0, 0) == -1) {
+			log_warn("cannot daemonize");
+			pidfile_remove(newconf->conf_pidfh);
+			exit(1);
+		}
+	}
+
+	/* Schedule iSNS update */
+	if (!TAILQ_EMPTY(&newconf->conf_isns))
+		set_timeout((newconf->conf_isns_period + 2) / 3, false);
+
+	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, newconf);
+			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("removing CTL iSCSI ports "
+			    "and terminating all connections");
+
+			oldconf = newconf;
+			newconf = conf_new();
+			if (debug > 0)
+				newconf->conf_debug = debug;
+			error = conf_apply(oldconf, newconf);
+			if (error != 0)
+				log_warnx("failed to apply configuration");
+			conf_delete(oldconf);
+			oldconf = NULL;
+
+			log_warnx("exiting on signal");
+			exit(0);
+		} else {
+			nchildren -= wait_for_children(false);
+			assert(nchildren >= 0);
+			if (timed_out()) {
+				set_timeout(0, false);
+				TAILQ_FOREACH(newns, &newconf->conf_isns, i_next)
+					isns_check(newns);
+				/* Schedule iSNS update */
+				if (!TAILQ_EMPTY(&newconf->conf_isns)) {
+					set_timeout((newconf->conf_isns_period
+					    + 2) / 3,
+					    false);
+				}
+			}
+		}
+	}
+	/* NOTREACHED */
+}


Property changes on: trunk/usr.sbin/ctld/ctld.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/ctld.h
===================================================================
--- trunk/usr.sbin/ctld/ctld.h	                        (rev 0)
+++ trunk/usr.sbin/ctld/ctld.h	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,450 @@
+/* $MidnightBSD$ */
+/*-
+ * 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: stable/10/usr.sbin/ctld/ctld.h 317352 2017-04-24 06:33:08Z mav $
+ */
+
+#ifndef CTLD_H
+#define	CTLD_H
+
+#include <sys/queue.h>
+#ifdef ICL_KERNEL_PROXY
+#include <sys/types.h>
+#endif
+#include <sys/socket.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	DEFAULT_CD_BLOCKSIZE		2048
+
+#define	MAX_LUNS			1024
+#define	MAX_NAME_LEN			223
+#define	MAX_DATA_SEGMENT_LENGTH		(128 * 1024)
+#define	MAX_BURST_LENGTH		16776192
+#define	SOCKBUF_SIZE			1048576
+
+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;
+};
+
+struct auth_name {
+	TAILQ_ENTRY(auth_name)		an_next;
+	struct auth_group		*an_auth_group;
+	char				*an_initator_name;
+};
+
+struct auth_portal {
+	TAILQ_ENTRY(auth_portal)	ap_next;
+	struct auth_group		*ap_auth_group;
+	char				*ap_initator_portal;
+	struct sockaddr_storage		ap_sa;
+	int				ap_mask;
+};
+
+#define	AG_TYPE_UNKNOWN			0
+#define	AG_TYPE_DENY			1
+#define	AG_TYPE_NO_AUTHENTICATION	2
+#define	AG_TYPE_CHAP			3
+#define	AG_TYPE_CHAP_MUTUAL		4
+
+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;
+	TAILQ_HEAD(, auth_name)		ag_names;
+	TAILQ_HEAD(, auth_portal)	ag_portals;
+};
+
+struct portal {
+	TAILQ_ENTRY(portal)		p_next;
+	struct portal_group		*p_portal_group;
+	bool				p_iser;
+	char				*p_listen;
+	struct addrinfo			*p_ai;
+#ifdef ICL_KERNEL_PROXY
+	int				p_id;
+#endif
+
+	TAILQ_HEAD(, target)		p_targets;
+	int				p_socket;
+};
+
+TAILQ_HEAD(options, option);
+
+#define	PG_FILTER_UNKNOWN		0
+#define	PG_FILTER_NONE			1
+#define	PG_FILTER_PORTAL		2
+#define	PG_FILTER_PORTAL_NAME		3
+#define	PG_FILTER_PORTAL_NAME_AUTH	4
+
+struct portal_group {
+	TAILQ_ENTRY(portal_group)	pg_next;
+	struct conf			*pg_conf;
+	struct options			pg_options;
+	char				*pg_name;
+	struct auth_group		*pg_discovery_auth_group;
+	int				pg_discovery_filter;
+	int				pg_foreign;
+	bool				pg_unassigned;
+	TAILQ_HEAD(, portal)		pg_portals;
+	TAILQ_HEAD(, port)		pg_ports;
+	char				*pg_redirection;
+
+	uint16_t			pg_tag;
+};
+
+struct pport {
+	TAILQ_ENTRY(pport)		pp_next;
+	TAILQ_HEAD(, port)		pp_ports;
+	struct conf			*pp_conf;
+	char				*pp_name;
+
+	uint32_t			pp_ctl_port;
+};
+
+struct port {
+	TAILQ_ENTRY(port)		p_next;
+	TAILQ_ENTRY(port)		p_pgs;
+	TAILQ_ENTRY(port)		p_pps;
+	TAILQ_ENTRY(port)		p_ts;
+	struct conf			*p_conf;
+	char				*p_name;
+	struct auth_group		*p_auth_group;
+	struct portal_group		*p_portal_group;
+	struct pport			*p_pport;
+	struct target			*p_target;
+
+	uint32_t			p_ctl_port;
+};
+
+struct option {
+	TAILQ_ENTRY(option)		o_next;
+	char				*o_name;
+	char				*o_value;
+};
+
+struct lun {
+	TAILQ_ENTRY(lun)		l_next;
+	struct conf			*l_conf;
+	struct options			l_options;
+	char				*l_name;
+	char				*l_backend;
+	uint8_t				l_device_type;
+	int				l_blocksize;
+	char				*l_device_id;
+	char				*l_path;
+	char				*l_scsiname;
+	char				*l_serial;
+	int64_t				l_size;
+
+	int				l_ctl_lun;
+};
+
+struct target {
+	TAILQ_ENTRY(target)		t_next;
+	struct conf			*t_conf;
+	struct lun			*t_luns[MAX_LUNS];
+	struct auth_group		*t_auth_group;
+	TAILQ_HEAD(, port)		t_ports;
+	char				*t_name;
+	char				*t_alias;
+	char				*t_redirection;
+};
+
+struct isns {
+	TAILQ_ENTRY(isns)		i_next;
+	struct conf			*i_conf;
+	char				*i_addr;
+	struct addrinfo			*i_ai;
+};
+
+struct conf {
+	char				*conf_pidfile_path;
+	TAILQ_HEAD(, lun)		conf_luns;
+	TAILQ_HEAD(, target)		conf_targets;
+	TAILQ_HEAD(, auth_group)	conf_auth_groups;
+	TAILQ_HEAD(, port)		conf_ports;
+	TAILQ_HEAD(, portal_group)	conf_portal_groups;
+	TAILQ_HEAD(, pport)		conf_pports;
+	TAILQ_HEAD(, isns)		conf_isns;
+	int				conf_isns_period;
+	int				conf_isns_timeout;
+	int				conf_debug;
+	int				conf_timeout;
+	int				conf_maxproc;
+
+#ifdef ICL_KERNEL_PROXY
+	int				conf_portal_id;
+#endif
+	struct pidfh			*conf_pidfh;
+
+	bool				conf_default_pg_defined;
+	bool				conf_default_ag_defined;
+	bool				conf_kernel_port_on;
+};
+
+#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 port		*conn_port;
+	struct target		*conn_target;
+	int			conn_socket;
+	int			conn_session_type;
+	char			*conn_initiator_name;
+	char			*conn_initiator_addr;
+	char			*conn_initiator_alias;
+	uint8_t			conn_initiator_isid[6];
+	struct sockaddr_storage	conn_initiator_sa;
+	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;
+	const char		*conn_user;
+	struct chap		*conn_chap;
+};
+
+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;
+};
+
+#define	CHAP_CHALLENGE_LEN	1024
+#define	CHAP_DIGEST_LEN		16 /* Equal to MD5 digest size. */
+
+struct chap {
+	unsigned char	chap_id;
+	char		chap_challenge[CHAP_CHALLENGE_LEN];
+	char		chap_response[CHAP_DIGEST_LEN];
+};
+
+struct rchap {
+	char		*rchap_secret;
+	unsigned char	rchap_id;
+	void		*rchap_challenge;
+	size_t		rchap_challenge_len;
+};
+
+struct chap		*chap_new(void);
+char			*chap_get_id(const struct chap *chap);
+char			*chap_get_challenge(const struct chap *chap);
+int			chap_receive(struct chap *chap, const char *response);
+int			chap_authenticate(struct chap *chap,
+			    const char *secret);
+void			chap_delete(struct chap *chap);
+
+struct rchap		*rchap_new(const char *secret);
+int			rchap_receive(struct rchap *rchap,
+			    const char *id, const char *challenge);
+char			*rchap_get_response(struct rchap *rchap);
+void			rchap_delete(struct rchap *rchap);
+
+struct conf		*conf_new(void);
+struct conf		*conf_new_from_file(const char *path, struct conf *old);
+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(const struct conf *conf,
+			    const char *name);
+int			auth_group_set_type(struct auth_group *ag,
+			    const char *type);
+
+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(const struct auth_group *ag,
+			    const char *user);
+
+const struct auth_name	*auth_name_new(struct auth_group *ag,
+			    const char *initiator_name);
+bool			auth_name_defined(const struct auth_group *ag);
+const struct auth_name	*auth_name_find(const struct auth_group *ag,
+			    const char *initiator_name);
+int			auth_name_check(const struct auth_group *ag,
+			    const char *initiator_name);
+
+const struct auth_portal	*auth_portal_new(struct auth_group *ag,
+				    const char *initiator_portal);
+bool			auth_portal_defined(const struct auth_group *ag);
+const struct auth_portal	*auth_portal_find(const struct auth_group *ag,
+				    const struct sockaddr_storage *sa);
+int				auth_portal_check(const struct auth_group *ag,
+				    const struct sockaddr_storage *sa);
+
+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(const struct conf *conf,
+			    const char *name);
+int			portal_group_add_listen(struct portal_group *pg,
+			    const char *listen, bool iser);
+int			portal_group_set_filter(struct portal_group *pg,
+			    const char *filter);
+int			portal_group_set_redirection(struct portal_group *pg,
+			    const char *addr);
+
+int			isns_new(struct conf *conf, const char *addr);
+void			isns_delete(struct isns *is);
+void			isns_register(struct isns *isns, struct isns *oldisns);
+void			isns_check(struct isns *isns);
+void			isns_deregister(struct isns *isns);
+
+struct pport		*pport_new(struct conf *conf, const char *name,
+			    uint32_t ctl_port);
+struct pport		*pport_find(const struct conf *conf, const char *name);
+struct pport		*pport_copy(struct pport *pport, struct conf *conf);
+void			pport_delete(struct pport *pport);
+
+struct port		*port_new(struct conf *conf, struct target *target,
+			    struct portal_group *pg);
+struct port		*port_new_pp(struct conf *conf, struct target *target,
+			    struct pport *pp);
+struct port		*port_find(const struct conf *conf, const char *name);
+struct port		*port_find_in_pg(const struct portal_group *pg,
+			    const char *target);
+void			port_delete(struct port *port);
+int			port_is_dummy(struct port *port);
+
+struct target		*target_new(struct conf *conf, const char *name);
+void			target_delete(struct target *target);
+struct target		*target_find(struct conf *conf,
+			    const char *name);
+int			target_set_redirection(struct target *target,
+			    const char *addr);
+
+struct lun		*lun_new(struct conf *conf, const char *name);
+void			lun_delete(struct lun *lun);
+struct lun		*lun_find(const struct conf *conf, const char *name);
+void			lun_set_backend(struct lun *lun, const char *value);
+void			lun_set_device_type(struct lun *lun, uint8_t 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_scsiname(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 option		*option_new(struct options *os,
+			    const char *name, const char *value);
+void			option_delete(struct options *os, struct option *co);
+struct option		*option_find(const struct options *os, const char *name);
+void			option_set(struct option *o, const char *value);
+
+void			kernel_init(void);
+int			kernel_lun_add(struct lun *lun);
+int			kernel_lun_modify(struct lun *lun);
+int			kernel_lun_remove(struct lun *lun);
+void			kernel_handoff(struct connection *conn);
+int			kernel_port_add(struct port *port);
+int			kernel_port_update(struct port *port, struct port *old);
+int			kernel_port_remove(struct port *port);
+void			kernel_capsicate(void);
+
+#ifdef ICL_KERNEL_PROXY
+void			kernel_listen(struct addrinfo *ai, bool iser,
+			    int portal_id);
+void			kernel_accept(int *connection_id, int *portal_id,
+			    struct sockaddr *client_sa,
+			    socklen_t *client_salen);
+void			kernel_send(struct pdu *pdu);
+void			kernel_receive(struct pdu *pdu);
+#endif
+
+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);
+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 __printflike(2, 3);
+void			log_errx(int, const char *, ...)
+			    __dead2 __printflike(2, 3);
+void			log_warn(const char *, ...) __printflike(1, 2);
+void			log_warnx(const char *, ...) __printflike(1, 2);
+void			log_debugx(const char *, ...) __printflike(1, 2);
+
+char			*checked_strdup(const char *);
+bool			valid_iscsi_name(const char *name);
+void			set_timeout(int timeout, int fatal);
+bool			timed_out(void);
+
+#endif /* !CTLD_H */


Property changes on: trunk/usr.sbin/ctld/ctld.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/discovery.c
===================================================================
--- trunk/usr.sbin/ctld/discovery.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/discovery.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,337 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/discovery.c 288704 2015-10-05 07:42:05Z mav $");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/socket.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 (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
+		log_errx(1, "received Text PDU with decreasing CmdSN: "
+		    "was %u, is %u", 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 %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
+		    conn->conn_statsn);
+	}
+	conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
+	if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
+		conn->conn_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 (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) {
+		log_errx(1, "received Logout PDU with decreasing CmdSN: "
+		    "was %u, is %u", 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 %u, should be %u", ntohl(bhslr->bhslr_expstatsn),
+		    conn->conn_statsn);
+	}
+	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
+	if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
+		conn->conn_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);
+}
+
+static void
+discovery_add_target(struct keys *response_keys, const struct target *targ)
+{
+	struct port *port;
+	struct portal *portal;
+	char *buf;
+	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+	struct addrinfo *ai;
+	int ret;
+
+	keys_add(response_keys, "TargetName", targ->t_name);
+	TAILQ_FOREACH(port, &targ->t_ports, p_ts) {
+	    if (port->p_portal_group == NULL)
+		continue;
+	    TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) {
+		ai = portal->p_ai;
+		ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
+		    hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
+		    NI_NUMERICHOST | NI_NUMERICSERV);
+		if (ret != 0) {
+			log_warnx("getnameinfo: %s", gai_strerror(ret));
+			continue;
+		}
+		switch (ai->ai_addr->sa_family) {
+		case AF_INET:
+			if (strcmp(hbuf, "0.0.0.0") == 0)
+				continue;
+			ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf,
+			    port->p_portal_group->pg_tag);
+			break;
+		case AF_INET6:
+			if (strcmp(hbuf, "::") == 0)
+				continue;
+			ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf,
+			    port->p_portal_group->pg_tag);
+			break;
+		default:
+			continue;
+		}
+		if (ret <= 0)
+		    log_err(1, "asprintf");
+		keys_add(response_keys, "TargetAddress", buf);
+		free(buf);
+	    }
+	}
+}
+
+static bool
+discovery_target_filtered_out(const struct connection *conn,
+    const struct port *port)
+{
+	const struct auth_group *ag;
+	const struct portal_group *pg;
+	const struct target *targ;
+	const struct auth *auth;
+	int error;
+
+	targ = port->p_target;
+	ag = port->p_auth_group;
+	if (ag == NULL)
+		ag = targ->t_auth_group;
+	pg = conn->conn_portal->p_portal_group;
+
+	assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN);
+
+	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL &&
+	    auth_portal_check(ag, &conn->conn_initiator_sa) != 0) {
+		log_debugx("initiator does not match initiator portals "
+		    "allowed for target \"%s\"; skipping", targ->t_name);
+		return (true);
+	}
+
+	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME &&
+	    auth_name_check(ag, conn->conn_initiator_name) != 0) {
+		log_debugx("initiator does not match initiator names "
+		    "allowed for target \"%s\"; skipping", targ->t_name);
+		return (true);
+	}
+
+	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH &&
+	    ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+		if (conn->conn_chap == NULL) {
+			assert(pg->pg_discovery_auth_group->ag_type ==
+			    AG_TYPE_NO_AUTHENTICATION);
+
+			log_debugx("initiator didn't authenticate, but target "
+			    "\"%s\" requires CHAP; skipping", targ->t_name);
+			return (true);
+		}
+
+		assert(conn->conn_user != NULL);
+		auth = auth_find(ag, conn->conn_user);
+		if (auth == NULL) {
+			log_debugx("CHAP user \"%s\" doesn't match target "
+			    "\"%s\"; skipping", conn->conn_user, targ->t_name);
+			return (true);
+		}
+
+		error = chap_authenticate(conn->conn_chap, auth->a_secret);
+		if (error != 0) {
+			log_debugx("password for CHAP user \"%s\" doesn't "
+			    "match target \"%s\"; skipping",
+			    conn->conn_user, targ->t_name);
+			return (true);
+		}
+	}
+
+	return (false);
+}
+
+void
+discovery(struct connection *conn)
+{
+	struct pdu *request, *response;
+	struct keys *request_keys, *response_keys;
+	const struct port *port;
+	const struct portal_group *pg;
+	const char *send_targets;
+
+	pg = conn->conn_portal->p_portal_group;
+
+	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(port, &pg->pg_ports, p_pgs) {
+			if (discovery_target_filtered_out(conn, port)) {
+				/* Ignore this target. */
+				continue;
+			}
+			discovery_add_target(response_keys, port->p_target);
+		}
+	} else {
+		port = port_find_in_pg(pg, send_targets);
+		if (port == NULL) {
+			log_debugx("initiator requested information on unknown "
+			    "target \"%s\"; returning nothing", send_targets);
+		} else {
+			if (discovery_target_filtered_out(conn, port)) {
+				/* Ignore this target. */
+			} else {
+				discovery_add_target(response_keys, port->p_target);
+			}
+		}
+	}
+	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");
+}


Property changes on: trunk/usr.sbin/ctld/discovery.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/isns.c
===================================================================
--- trunk/usr.sbin/ctld/isns.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/isns.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,253 @@
+/* $MidnightBSD$ */
+/*-
+ * Copyright (c) 2014 Alexander Motin <mav at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/isns.c 288704 2015-10-05 07:42:05Z mav $");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/endian.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ctld.h"
+#include "isns.h"
+
+struct isns_req *
+isns_req_alloc(void)
+{
+	struct isns_req *req;
+
+	req = calloc(sizeof(struct isns_req), 1);
+	if (req == NULL) {
+		log_err(1, "calloc");
+		return (NULL);
+	}
+	req->ir_buflen = sizeof(struct isns_hdr);
+	req->ir_usedlen = 0;
+	req->ir_buf = calloc(req->ir_buflen, 1);
+	if (req->ir_buf == NULL) {
+		free(req);
+		log_err(1, "calloc");
+		return (NULL);
+	}
+	return (req);
+}
+
+struct isns_req *
+isns_req_create(uint16_t func, uint16_t flags)
+{
+	struct isns_req *req;
+	struct isns_hdr *hdr;
+
+	req = isns_req_alloc();
+	req->ir_usedlen = sizeof(struct isns_hdr);
+	hdr = (struct isns_hdr *)req->ir_buf;
+	be16enc(hdr->ih_version, ISNS_VERSION);
+	be16enc(hdr->ih_function, func);
+	be16enc(hdr->ih_flags, flags);
+	return (req);
+}
+
+void
+isns_req_free(struct isns_req *req)
+{
+
+	free(req->ir_buf);
+	free(req);
+}
+
+static int
+isns_req_getspace(struct isns_req *req, uint32_t len)
+{
+	void *newbuf;
+	int newlen;
+
+	if (req->ir_usedlen + len <= req->ir_buflen)
+		return (0);
+	newlen = 1 << flsl(req->ir_usedlen + len);
+	newbuf = realloc(req->ir_buf, newlen);
+	if (newbuf == NULL) {
+		log_err(1, "realloc");
+		return (1);
+	}
+	req->ir_buf = newbuf;
+	req->ir_buflen = newlen;
+	return (0);
+}
+
+void
+isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len,
+    const void *value)
+{
+	struct isns_tlv *tlv;
+	uint32_t vlen;
+
+	vlen = len + ((len & 3) ? (4 - (len & 3)) : 0);
+	isns_req_getspace(req, sizeof(*tlv) + vlen);
+	tlv = (struct isns_tlv *)&req->ir_buf[req->ir_usedlen];
+	be32enc(tlv->it_tag, tag);
+	be32enc(tlv->it_length, vlen);
+	memcpy(tlv->it_value, value, len);
+	if (vlen != len)
+		memset(&tlv->it_value[len], 0, vlen - len);
+	req->ir_usedlen += sizeof(*tlv) + vlen;
+}
+
+void
+isns_req_add_delim(struct isns_req *req)
+{
+
+	isns_req_add(req, 0, 0, NULL);
+}
+
+void
+isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value)
+{
+
+	isns_req_add(req, tag, strlen(value) + 1, value);
+}
+
+void
+isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value)
+{
+	uint32_t beval;
+
+	be32enc(&beval, value);
+	isns_req_add(req, tag, sizeof(value), &beval);
+}
+
+void
+isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai)
+{
+	struct sockaddr_in *in4;
+	struct sockaddr_in6 *in6;
+	uint8_t buf[16];
+
+	switch (ai->ai_addr->sa_family) {
+	case AF_INET:
+		in4 = (struct sockaddr_in *)(void *)ai->ai_addr;
+		memset(buf, 0, 10);
+		buf[10] = 0xff;
+		buf[11] = 0xff;
+		memcpy(&buf[12], &in4->sin_addr, sizeof(in4->sin_addr));
+		isns_req_add(req, tag, sizeof(buf), buf);
+		break;
+	case AF_INET6:
+		in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr;
+		isns_req_add(req, tag, sizeof(in6->sin6_addr), &in6->sin6_addr);
+		break;
+	default:
+		log_errx(1, "Unsupported address family %d",
+		    ai->ai_addr->sa_family);
+	}
+}
+
+void
+isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai)
+{
+	struct sockaddr_in *in4;
+	struct sockaddr_in6 *in6;
+	uint32_t buf;
+
+	switch (ai->ai_addr->sa_family) {
+	case AF_INET:
+		in4 = (struct sockaddr_in *)(void *)ai->ai_addr;
+		be32enc(&buf, ntohs(in4->sin_port));
+		isns_req_add(req, tag, sizeof(buf), &buf);
+		break;
+	case AF_INET6:
+		in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr;
+		be32enc(&buf, ntohs(in6->sin6_port));
+		isns_req_add(req, tag, sizeof(buf), &buf);
+		break;
+	default:
+		log_errx(1, "Unsupported address family %d",
+		    ai->ai_addr->sa_family);
+	}
+}
+
+int
+isns_req_send(int s, struct isns_req *req)
+{
+	struct isns_hdr *hdr;
+	int res;
+
+	hdr = (struct isns_hdr *)req->ir_buf;
+	be16enc(hdr->ih_length, req->ir_usedlen - sizeof(*hdr));
+	be16enc(hdr->ih_flags, be16dec(hdr->ih_flags) |
+	    ISNS_FLAG_LAST | ISNS_FLAG_FIRST);
+	be16enc(hdr->ih_transaction, 0);
+	be16enc(hdr->ih_sequence, 0);
+
+	res = write(s, req->ir_buf, req->ir_usedlen);
+	return ((res < 0) ? -1 : 0);
+}
+
+int
+isns_req_receive(int s, struct isns_req *req)
+{
+	struct isns_hdr *hdr;
+	ssize_t res, len;
+
+	req->ir_usedlen = 0;
+	isns_req_getspace(req, sizeof(*hdr));
+	res = read(s, req->ir_buf, sizeof(*hdr));
+	if (res < (ssize_t)sizeof(*hdr))
+		return (-1);
+	req->ir_usedlen = sizeof(*hdr);
+	hdr = (struct isns_hdr *)req->ir_buf;
+	if (be16dec(hdr->ih_version) != ISNS_VERSION)
+		return (-1);
+	if ((be16dec(hdr->ih_flags) & (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) !=
+	    (ISNS_FLAG_LAST | ISNS_FLAG_FIRST))
+		return (-1);
+	len = be16dec(hdr->ih_length);
+	isns_req_getspace(req, len);
+	res = read(s, &req->ir_buf[req->ir_usedlen], len);
+	if (res < len)
+		return (-1);
+	req->ir_usedlen += len;
+	return (0);
+}
+
+uint32_t
+isns_req_get_status(struct isns_req *req)
+{
+
+	if (req->ir_usedlen < sizeof(struct isns_hdr) + 4)
+		return (-1);
+	return (be32dec(&req->ir_buf[sizeof(struct isns_hdr)]));
+}


Property changes on: trunk/usr.sbin/ctld/isns.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/isns.h
===================================================================
--- trunk/usr.sbin/ctld/isns.h	                        (rev 0)
+++ trunk/usr.sbin/ctld/isns.h	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,93 @@
+/* $MidnightBSD$ */
+/*-
+ * Copyright (c) 2014 Alexander Motin <mav at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: stable/10/usr.sbin/ctld/isns.h 274939 2014-11-24 00:47:04Z mav $
+ */
+
+#ifndef	_ISNS_H
+#define	_ISNS_H
+
+#define	ISNS_VERSION		0x0001
+
+#define	ISNS_FUNC_DEVATTRREG	0x0001
+#define	ISNS_FUNC_DEVATTRQRY	0x0002
+#define	ISNS_FUNC_DEVGETNEXT	0x0003
+#define	ISNS_FUNC_DEVDEREG	0x0004
+#define	ISNS_FUNC_SCNREG	0x0005
+#define	ISNS_FUNC_SCNDEREG	0x0006
+#define	ISNS_FUNC_SCNEVENT	0x0007
+#define	ISNS_FUNC_SCN		0x0008
+#define	ISNS_FUNC_DDREG		0x0009
+#define	ISNS_FUNC_DDDEREG	0x000a
+#define	ISNS_FUNC_DDSREG	0x000b
+#define	ISNS_FUNC_DDSDEREG	0x000c
+#define	ISNS_FUNC_ESI		0x000d
+#define	ISNS_FUNC_HEARTBEAT	0x000e
+#define	ISNS_FUNC_RESPONSE	0x8000
+
+#define	ISNS_FLAG_CLIENT	0x8000
+#define	ISNS_FLAG_SERVER	0x4000
+#define	ISNS_FLAG_AUTH		0x2000
+#define	ISNS_FLAG_REPLACE	0x1000
+#define	ISNS_FLAG_LAST		0x0800
+#define	ISNS_FLAG_FIRST		0x0400
+
+struct isns_hdr {
+	uint8_t	ih_version[2];
+	uint8_t	ih_function[2];
+	uint8_t	ih_length[2];
+	uint8_t	ih_flags[2];
+	uint8_t	ih_transaction[2];
+	uint8_t	ih_sequence[2];
+};
+
+struct isns_tlv {
+	uint8_t	it_tag[4];
+	uint8_t	it_length[4];
+	uint8_t	it_value[];
+};
+
+struct isns_req {
+	u_int	ir_buflen;
+	u_int	ir_usedlen;
+	uint8_t	*ir_buf;
+};
+
+struct isns_req * isns_req_alloc(void);
+struct isns_req * isns_req_create(uint16_t func, uint16_t flags);
+void isns_req_free(struct isns_req *req);
+void isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len,
+    const void *value);
+void isns_req_add_delim(struct isns_req *req);
+void isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value);
+void isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value);
+void isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai);
+void isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai);
+int isns_req_send(int s, struct isns_req *req);
+int isns_req_receive(int s, struct isns_req *req);
+uint32_t isns_req_get_status(struct isns_req *req);
+
+#endif /* _ISNS_H */


Property changes on: trunk/usr.sbin/ctld/isns.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/kernel.c
===================================================================
--- trunk/usr.sbin/ctld/kernel.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/kernel.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,1234 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/kernel.c 316253 2017-03-30 06:13:54Z ngie $");
+
+#include <sys/param.h>
+#include <sys/capsicum.h>
+#include <sys/callout.h>
+#include <sys/ioctl.h>
+#include <sys/linker.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+#include <sys/stat.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_backend.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_util.h>
+#include <cam/ctl/ctl_scsi_all.h>
+
+#include "ctld.h"
+
+#ifdef ICL_KERNEL_PROXY
+#include <netdb.h>
+#endif
+
+extern bool proxy_mode;
+
+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 && errno == ENOENT) {
+		saved_errno = errno;
+		retval = kldload("ctl");
+		if (retval != -1)
+			ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
+		else
+			errno = saved_errno;
+	}
+	if (ctl_fd < 0)
+		log_err(1, "failed to open %s", CTL_DEFAULT_DEV);
+}
+
+/*
+ * 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;
+	uint8_t device_type;
+	uint64_t size_blocks;
+	uint32_t blocksize;
+	char *serial_number;
+	char *device_id;
+	char *ctld_name;
+	STAILQ_HEAD(,cctl_lun_nv) attr_list;
+	STAILQ_ENTRY(cctl_lun) links;
+};
+
+struct cctl_port {
+	uint32_t port_id;
+	char *port_frontend;
+	char *port_name;
+	int pp;
+	int vp;
+	int cfiscsi_state;
+	char *cfiscsi_target;
+	uint16_t cfiscsi_portal_group_tag;
+	char *ctld_portal_group_name;
+	STAILQ_HEAD(,cctl_lun_nv) attr_list;
+	STAILQ_ENTRY(cctl_port) links;
+};
+
+struct cctl_devlist_data {
+	int num_luns;
+	STAILQ_HEAD(,cctl_lun) lun_list;
+	struct cctl_lun *cur_lun;
+	int num_ports;
+	STAILQ_HEAD(,cctl_port) port_list;
+	struct cctl_port *cur_port;
+	int level;
+	struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_start_element(void *user_data, const char *name, const char **attr)
+{
+	int i;
+	struct cctl_devlist_data *devlist;
+	struct cctl_lun *cur_lun;
+
+	devlist = (struct cctl_devlist_data *)user_data;
+	cur_lun = devlist->cur_lun;
+	devlist->level++;
+	if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) /
+	    sizeof(devlist->cur_sb[0])))
+		log_errx(1, "%s: too many nesting levels, %zd max", __func__,
+		     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, "lun_type") == 0) {
+		cur_lun->device_type = strtoull(str, NULL, 0);
+	} 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, "ctld_name") == 0) {
+		cur_lun->ctld_name = str;
+		str = NULL;
+	} else if (strcmp(name, "lun") == 0) {
+		devlist->cur_lun = NULL;
+	} else if (strcmp(name, "ctllunlist") == 0) {
+		/* Nothing. */
+	} else {
+		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_start_pelement(void *user_data, const char *name, const char **attr)
+{
+	int i;
+	struct cctl_devlist_data *devlist;
+	struct cctl_port *cur_port;
+
+	devlist = (struct cctl_devlist_data *)user_data;
+	cur_port = devlist->cur_port;
+	devlist->level++;
+	if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) /
+	    sizeof(devlist->cur_sb[0])))
+		log_errx(1, "%s: too many nesting levels, %zd max", __func__,
+		     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, "targ_port") == 0) {
+		if (cur_port != NULL)
+			log_errx(1, "%s: improper port element nesting (%s)",
+			    __func__, name);
+
+		cur_port = calloc(1, sizeof(*cur_port));
+		if (cur_port == NULL)
+			log_err(1, "%s: cannot allocate %zd bytes", __func__,
+			    sizeof(*cur_port));
+
+		devlist->num_ports++;
+		devlist->cur_port = cur_port;
+
+		STAILQ_INIT(&cur_port->attr_list);
+		STAILQ_INSERT_TAIL(&devlist->port_list, cur_port, links);
+
+		for (i = 0; attr[i] != NULL; i += 2) {
+			if (strcmp(attr[i], "id") == 0) {
+				cur_port->port_id = strtoul(attr[i+1], NULL, 0);
+			} else {
+				log_errx(1, "%s: invalid LUN attribute %s = %s",
+				     __func__, attr[i], attr[i+1]);
+			}
+		}
+	}
+}
+
+static void
+cctl_end_pelement(void *user_data, const char *name)
+{
+	struct cctl_devlist_data *devlist;
+	struct cctl_port *cur_port;
+	char *str;
+
+	devlist = (struct cctl_devlist_data *)user_data;
+	cur_port = devlist->cur_port;
+
+	if ((cur_port == NULL)
+	 && (strcmp(name, "ctlportlist") != 0))
+		log_errx(1, "%s: cur_port == NULL! (name = %s)", __func__, name);
+
+	if (devlist->cur_sb[devlist->level] == NULL)
+		log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+		     devlist->level, name);
+
+	sbuf_finish(devlist->cur_sb[devlist->level]);
+	str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level]));
+
+	if (strlen(str) == 0) {
+		free(str);
+		str = NULL;
+	}
+
+	sbuf_delete(devlist->cur_sb[devlist->level]);
+	devlist->cur_sb[devlist->level] = NULL;
+	devlist->level--;
+
+	if (strcmp(name, "frontend_type") == 0) {
+		cur_port->port_frontend = str;
+		str = NULL;
+	} else if (strcmp(name, "port_name") == 0) {
+		cur_port->port_name = str;
+		str = NULL;
+	} else if (strcmp(name, "physical_port") == 0) {
+		cur_port->pp = strtoul(str, NULL, 0);
+	} else if (strcmp(name, "virtual_port") == 0) {
+		cur_port->vp = strtoul(str, NULL, 0);
+	} else if (strcmp(name, "cfiscsi_target") == 0) {
+		cur_port->cfiscsi_target = str;
+		str = NULL;
+	} else if (strcmp(name, "cfiscsi_state") == 0) {
+		cur_port->cfiscsi_state = strtoul(str, NULL, 0);
+	} else if (strcmp(name, "cfiscsi_portal_group_tag") == 0) {
+		cur_port->cfiscsi_portal_group_tag = strtoul(str, NULL, 0);
+	} else if (strcmp(name, "ctld_portal_group_name") == 0) {
+		cur_port->ctld_portal_group_name = str;
+		str = NULL;
+	} else if (strcmp(name, "targ_port") == 0) {
+		devlist->cur_port = NULL;
+	} else if (strcmp(name, "ctlportlist") == 0) {
+		/* Nothing. */
+	} else {
+		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_port->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 portal_group *pg;
+	struct pport *pp;
+	struct port *cp;
+	struct lun *cl;
+	struct option *o;
+	struct ctl_lun_list list;
+	struct cctl_devlist_data devlist;
+	struct cctl_lun *lun;
+	struct cctl_port *port;
+	XML_Parser parser;
+	char *str, *name;
+	int len, retval;
+
+	bzero(&devlist, sizeof(devlist));
+	STAILQ_INIT(&devlist.lun_list);
+	STAILQ_INIT(&devlist.port_list);
+
+	log_debugx("obtaining previously configured CTL luns from the kernel");
+
+	str = NULL;
+	len = 4096;
+retry:
+	str = realloc(str, len);
+	if (str == NULL)
+		log_err(1, "realloc");
+
+	bzero(&list, sizeof(list));
+	list.alloc_len = len;
+	list.status = CTL_LUN_LIST_NONE;
+	list.lun_xml = str;
+
+	if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) {
+		log_warn("error issuing CTL_LUN_LIST ioctl");
+		free(str);
+		return (NULL);
+	}
+
+	if (list.status == CTL_LUN_LIST_ERROR) {
+		log_warnx("error returned from CTL_LUN_LIST ioctl: %s",
+		    list.error_str);
+		free(str);
+		return (NULL);
+	}
+
+	if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+		len = len << 1;
+		goto retry;
+	}
+
+	parser = XML_ParserCreate(NULL);
+	if (parser == NULL) {
+		log_warnx("unable to create XML parser");
+		free(str);
+		return (NULL);
+	}
+
+	XML_SetUserData(parser, &devlist);
+	XML_SetElementHandler(parser, cctl_start_element, cctl_end_element);
+	XML_SetCharacterDataHandler(parser, cctl_char_handler);
+
+	retval = XML_Parse(parser, str, strlen(str), 1);
+	XML_ParserFree(parser);
+	free(str);
+	if (retval != 1) {
+		log_warnx("XML_Parse failed");
+		return (NULL);
+	}
+
+	str = NULL;
+	len = 4096;
+retry_port:
+	str = realloc(str, len);
+	if (str == NULL)
+		log_err(1, "realloc");
+
+	bzero(&list, sizeof(list));
+	list.alloc_len = len;
+	list.status = CTL_LUN_LIST_NONE;
+	list.lun_xml = str;
+
+	if (ioctl(ctl_fd, CTL_PORT_LIST, &list) == -1) {
+		log_warn("error issuing CTL_PORT_LIST ioctl");
+		free(str);
+		return (NULL);
+	}
+
+	if (list.status == CTL_LUN_LIST_ERROR) {
+		log_warnx("error returned from CTL_PORT_LIST ioctl: %s",
+		    list.error_str);
+		free(str);
+		return (NULL);
+	}
+
+	if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+		len = len << 1;
+		goto retry_port;
+	}
+
+	parser = XML_ParserCreate(NULL);
+	if (parser == NULL) {
+		log_warnx("unable to create XML parser");
+		free(str);
+		return (NULL);
+	}
+
+	XML_SetUserData(parser, &devlist);
+	XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement);
+	XML_SetCharacterDataHandler(parser, cctl_char_handler);
+
+	retval = XML_Parse(parser, str, strlen(str), 1);
+	XML_ParserFree(parser);
+	free(str);
+	if (retval != 1) {
+		log_warnx("XML_Parse failed");
+		return (NULL);
+	}
+
+	conf = conf_new();
+
+	name = NULL;
+	STAILQ_FOREACH(port, &devlist.port_list, links) {
+		if (strcmp(port->port_frontend, "ha") == 0)
+			continue;
+		if (name)
+			free(name);
+		if (port->pp == 0 && port->vp == 0)
+			name = checked_strdup(port->port_name);
+		else if (port->vp == 0)
+			asprintf(&name, "%s/%d", port->port_name, port->pp);
+		else
+			asprintf(&name, "%s/%d/%d", port->port_name, port->pp,
+			    port->vp);
+
+		if (port->cfiscsi_target == NULL) {
+			log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
+			    port->port_id, name);
+			pp = pport_find(conf, name);
+			if (pp == NULL) {
+#if 0
+				log_debugx("found new kernel port %u \"%s\"",
+				    port->port_id, name);
+#endif
+				pp = pport_new(conf, name, port->port_id);
+				if (pp == NULL) {
+					log_warnx("pport_new failed");
+					continue;
+				}
+			}
+			continue;
+		}
+		if (port->cfiscsi_state != 1) {
+			log_debugx("CTL port %ju is not active (%d); ignoring",
+			    (uintmax_t)port->port_id, port->cfiscsi_state);
+			continue;
+		}
+
+		targ = target_find(conf, port->cfiscsi_target);
+		if (targ == NULL) {
+#if 0
+			log_debugx("found new kernel target %s for CTL port %ld",
+			    port->cfiscsi_target, port->port_id);
+#endif
+			targ = target_new(conf, port->cfiscsi_target);
+			if (targ == NULL) {
+				log_warnx("target_new failed");
+				continue;
+			}
+		}
+
+		if (port->ctld_portal_group_name == NULL)
+			continue;
+		pg = portal_group_find(conf, port->ctld_portal_group_name);
+		if (pg == NULL) {
+#if 0
+			log_debugx("found new kernel portal group %s for CTL port %ld",
+			    port->ctld_portal_group_name, port->port_id);
+#endif
+			pg = portal_group_new(conf, port->ctld_portal_group_name);
+			if (pg == NULL) {
+				log_warnx("portal_group_new failed");
+				continue;
+			}
+		}
+		pg->pg_tag = port->cfiscsi_portal_group_tag;
+		cp = port_new(conf, targ, pg);
+		if (cp == NULL) {
+			log_warnx("port_new failed");
+			continue;
+		}
+		cp->p_ctl_port = port->port_id;
+	}
+	if (name)
+		free(name);
+
+	STAILQ_FOREACH(lun, &devlist.lun_list, links) {
+		struct cctl_lun_nv *nv;
+
+		if (lun->ctld_name == NULL) {
+			log_debugx("CTL lun %ju wasn't managed by ctld; "
+			    "ignoring", (uintmax_t)lun->lun_id);
+			continue;
+		}
+
+		cl = lun_find(conf, lun->ctld_name);
+		if (cl != NULL) {
+			log_warnx("found CTL lun %ju \"%s\", "
+			    "also backed by CTL lun %d; ignoring",
+			    (uintmax_t)lun->lun_id, lun->ctld_name,
+			    cl->l_ctl_lun);
+			continue;
+		}
+
+		log_debugx("found CTL lun %ju \"%s\"",
+		    (uintmax_t)lun->lun_id, lun->ctld_name);
+
+		cl = lun_new(conf, lun->ctld_name);
+		if (cl == NULL) {
+			log_warnx("lun_new failed");
+			continue;
+		}
+		lun_set_backend(cl, lun->backend_type);
+		lun_set_device_type(cl, lun->device_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;
+			}
+			o = option_new(&cl->l_options, nv->name, nv->value);
+			if (o == NULL)
+				log_warnx("unable to add CTL lun option %s "
+				    "for CTL lun %ju \"%s\"",
+				    nv->name, (uintmax_t) lun->lun_id,
+				    cl->l_name);
+		}
+	}
+
+	return (conf);
+}
+
+static void
+str_arg(struct ctl_be_arg *arg, const char *name, const char *value)
+{
+
+	arg->namelen = strlen(name) + 1;
+	arg->name = __DECONST(char *, name);
+	arg->vallen = strlen(value) + 1;
+	arg->value = __DECONST(char *, value);
+	arg->flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
+}
+
+int
+kernel_lun_add(struct lun *lun)
+{
+	struct option *o;
+	struct ctl_lun_req req;
+	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;
+
+	if (lun->l_ctl_lun >= 0) {
+		req.reqdata.create.req_lun_id = lun->l_ctl_lun;
+		req.reqdata.create.flags |= CTL_LUN_FLAG_ID_REQ;
+	}
+
+	req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE;
+	req.reqdata.create.device_type = lun->l_device_type;
+
+	if (lun->l_serial != NULL) {
+		strncpy(req.reqdata.create.serial_num, lun->l_serial,
+			sizeof(req.reqdata.create.serial_num));
+		req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM;
+	}
+
+	if (lun->l_device_id != NULL) {
+		strncpy(req.reqdata.create.device_id, lun->l_device_id,
+			sizeof(req.reqdata.create.device_id));
+		req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID;
+	}
+
+	if (lun->l_path != NULL) {
+		o = option_find(&lun->l_options, "file");
+		if (o != NULL) {
+			option_set(o, lun->l_path);
+		} else {
+			o = option_new(&lun->l_options, "file", lun->l_path);
+			assert(o != NULL);
+		}
+	}
+
+	o = option_find(&lun->l_options, "ctld_name");
+	if (o != NULL) {
+		option_set(o, lun->l_name);
+	} else {
+		o = option_new(&lun->l_options, "ctld_name", lun->l_name);
+		assert(o != NULL);
+	}
+
+	o = option_find(&lun->l_options, "scsiname");
+	if (o == NULL && lun->l_scsiname != NULL) {
+		o = option_new(&lun->l_options, "scsiname", lun->l_scsiname);
+		assert(o != NULL);
+	}
+
+	num_options = 0;
+	TAILQ_FOREACH(o, &lun->l_options, o_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(o, &lun->l_options, o_next) {
+			str_arg(&req.be_args[i], o->o_name, o->o_value);
+			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);
+	}
+
+	switch (req.status) {
+	case CTL_LUN_ERROR:
+		log_warnx("LUN creation error: %s", req.error_str);
+		return (1);
+	case CTL_LUN_WARNING:
+		log_warnx("LUN creation warning: %s", req.error_str);
+		break;
+	case CTL_LUN_OK:
+		break;
+	default:
+		log_warnx("unknown LUN creation status: %d",
+		    req.status);
+		return (1);
+	}
+
+	lun_set_ctl_lun(lun, req.reqdata.create.req_lun_id);
+	return (0);
+}
+
+int
+kernel_lun_modify(struct lun *lun)
+{
+	struct option *o;
+	struct ctl_lun_req req;
+	int error, i, num_options;
+
+	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;
+
+	num_options = 0;
+	TAILQ_FOREACH(o, &lun->l_options, o_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(o, &lun->l_options, o_next) {
+			str_arg(&req.be_args[i], o->o_name, o->o_value);
+			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);
+	}
+
+	switch (req.status) {
+	case CTL_LUN_ERROR:
+		log_warnx("LUN modification error: %s", req.error_str);
+		return (1);
+	case CTL_LUN_WARNING:
+		log_warnx("LUN modification warning: %s", req.error_str);
+		break;
+	case CTL_LUN_OK:
+		break;
+	default:
+		log_warnx("unknown LUN modification status: %d",
+		    req.status);
+		return (1);
+	}
+
+	return (0);
+}
+
+int
+kernel_lun_remove(struct lun *lun)
+{
+	struct ctl_lun_req req;
+
+	bzero(&req, sizeof(req));
+
+	strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
+	req.reqtype = CTL_LUNREQ_RM;
+
+	req.reqdata.rm.lun_id = lun->l_ctl_lun;
+
+	if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) {
+		log_warn("error issuing CTL_LUN_REQ ioctl");
+		return (1);
+	}
+
+	switch (req.status) {
+	case CTL_LUN_ERROR:
+		log_warnx("LUN removal error: %s", req.error_str);
+		return (1);
+	case CTL_LUN_WARNING:
+		log_warnx("LUN removal warning: %s", req.error_str);
+		break;
+	case CTL_LUN_OK:
+		break;
+	default:
+		log_warnx("unknown LUN removal status: %d", req.status);
+		return (1);
+	}
+
+	return (0);
+}
+
+void
+kernel_handoff(struct connection *conn)
+{
+	struct ctl_iscsi req;
+
+	bzero(&req, sizeof(req));
+
+	req.type = CTL_ISCSI_HANDOFF;
+	strlcpy(req.data.handoff.initiator_name,
+	    conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name));
+	strlcpy(req.data.handoff.initiator_addr,
+	    conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr));
+	if (conn->conn_initiator_alias != NULL) {
+		strlcpy(req.data.handoff.initiator_alias,
+		    conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias));
+	}
+	memcpy(req.data.handoff.initiator_isid, conn->conn_initiator_isid,
+	    sizeof(req.data.handoff.initiator_isid));
+	strlcpy(req.data.handoff.target_name,
+	    conn->conn_target->t_name, sizeof(req.data.handoff.target_name));
+#ifdef ICL_KERNEL_PROXY
+	if (proxy_mode)
+		req.data.handoff.connection_id = conn->conn_socket;
+	else
+		req.data.handoff.socket = conn->conn_socket;
+#else
+	req.data.handoff.socket = conn->conn_socket;
+#endif
+	req.data.handoff.portal_group_tag =
+	    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_add(struct port *port)
+{
+	struct option *o;
+	struct ctl_port_entry entry;
+	struct ctl_req req;
+	struct ctl_lun_map lm;
+	struct target *targ = port->p_target;
+	struct portal_group *pg = port->p_portal_group;
+	char tagstr[16];
+	int error, i, n;
+
+	/* Create iSCSI port. */
+	if (port->p_portal_group) {
+		bzero(&req, sizeof(req));
+		strlcpy(req.driver, "iscsi", sizeof(req.driver));
+		req.reqtype = CTL_REQ_CREATE;
+		req.num_args = 5;
+		TAILQ_FOREACH(o, &pg->pg_options, o_next)
+			req.num_args++;
+		req.args = malloc(req.num_args * sizeof(*req.args));
+		if (req.args == NULL)
+			log_err(1, "malloc");
+		n = 0;
+		req.args[n].namelen = sizeof("port_id");
+		req.args[n].name = __DECONST(char *, "port_id");
+		req.args[n].vallen = sizeof(port->p_ctl_port);
+		req.args[n].value = &port->p_ctl_port;
+		req.args[n++].flags = CTL_BEARG_WR;
+		str_arg(&req.args[n++], "cfiscsi_target", targ->t_name);
+		snprintf(tagstr, sizeof(tagstr), "%d", pg->pg_tag);
+		str_arg(&req.args[n++], "cfiscsi_portal_group_tag", tagstr);
+		if (targ->t_alias)
+			str_arg(&req.args[n++], "cfiscsi_target_alias", targ->t_alias);
+		str_arg(&req.args[n++], "ctld_portal_group_name", pg->pg_name);
+		TAILQ_FOREACH(o, &pg->pg_options, o_next)
+			str_arg(&req.args[n++], o->o_name, o->o_value);
+		req.num_args = n;
+		error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
+		free(req.args);
+		if (error != 0) {
+			log_warn("error issuing CTL_PORT_REQ ioctl");
+			return (1);
+		}
+		if (req.status == CTL_LUN_ERROR) {
+			log_warnx("error returned from port creation request: %s",
+			    req.error_str);
+			return (1);
+		}
+		if (req.status != CTL_LUN_OK) {
+			log_warnx("unknown port creation request status %d",
+			    req.status);
+			return (1);
+		}
+	} else if (port->p_pport) {
+		port->p_ctl_port = port->p_pport->pp_ctl_port;
+
+		if (strncmp(targ->t_name, "naa.", 4) == 0 &&
+		    strlen(targ->t_name) == 20) {
+			bzero(&entry, sizeof(entry));
+			entry.port_type = CTL_PORT_NONE;
+			entry.targ_port = port->p_ctl_port;
+			entry.flags |= CTL_PORT_WWNN_VALID;
+			entry.wwnn = strtoull(targ->t_name + 4, NULL, 16);
+			if (ioctl(ctl_fd, CTL_SET_PORT_WWNS, &entry) == -1)
+				log_warn("CTL_SET_PORT_WWNS ioctl failed");
+		}
+	}
+
+	/* Explicitly enable mapping to block any access except allowed. */
+	lm.port = port->p_ctl_port;
+	lm.plun = UINT32_MAX;
+	lm.lun = 0;
+	error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+	if (error != 0)
+		log_warn("CTL_LUN_MAP ioctl failed");
+
+	/* Map configured LUNs */
+	for (i = 0; i < MAX_LUNS; i++) {
+		if (targ->t_luns[i] == NULL)
+			continue;
+		lm.port = port->p_ctl_port;
+		lm.plun = i;
+		lm.lun = targ->t_luns[i]->l_ctl_lun;
+		error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+		if (error != 0)
+			log_warn("CTL_LUN_MAP ioctl failed");
+	}
+
+	/* Enable port */
+	bzero(&entry, sizeof(entry));
+	entry.targ_port = port->p_ctl_port;
+	error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry);
+	if (error != 0) {
+		log_warn("CTL_ENABLE_PORT ioctl failed");
+		return (-1);
+	}
+
+	return (0);
+}
+
+int
+kernel_port_update(struct port *port, struct port *oport)
+{
+	struct ctl_lun_map lm;
+	struct target *targ = port->p_target;
+	struct target *otarg = oport->p_target;
+	int error, i;
+	uint32_t olun;
+
+	/* Map configured LUNs and unmap others */
+	for (i = 0; i < MAX_LUNS; i++) {
+		lm.port = port->p_ctl_port;
+		lm.plun = i;
+		if (targ->t_luns[i] == NULL)
+			lm.lun = UINT32_MAX;
+		else
+			lm.lun = targ->t_luns[i]->l_ctl_lun;
+		if (otarg->t_luns[i] == NULL)
+			olun = UINT32_MAX;
+		else
+			olun = otarg->t_luns[i]->l_ctl_lun;
+		if (lm.lun == olun)
+			continue;
+		error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+		if (error != 0)
+			log_warn("CTL_LUN_MAP ioctl failed");
+	}
+	return (0);
+}
+
+int
+kernel_port_remove(struct port *port)
+{
+	struct ctl_port_entry entry;
+	struct ctl_lun_map lm;
+	struct ctl_req req;
+	char tagstr[16];
+	struct target *targ = port->p_target;
+	struct portal_group *pg = port->p_portal_group;
+	int error;
+
+	/* Disable port */
+	bzero(&entry, sizeof(entry));
+	entry.targ_port = port->p_ctl_port;
+	error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry);
+	if (error != 0) {
+		log_warn("CTL_DISABLE_PORT ioctl failed");
+		return (-1);
+	}
+
+	/* Remove iSCSI port. */
+	if (port->p_portal_group) {
+		bzero(&req, sizeof(req));
+		strlcpy(req.driver, "iscsi", sizeof(req.driver));
+		req.reqtype = CTL_REQ_REMOVE;
+		req.num_args = 2;
+		req.args = malloc(req.num_args * sizeof(*req.args));
+		if (req.args == NULL)
+			log_err(1, "malloc");
+		str_arg(&req.args[0], "cfiscsi_target", targ->t_name);
+		snprintf(tagstr, sizeof(tagstr), "%d", pg->pg_tag);
+		str_arg(&req.args[1], "cfiscsi_portal_group_tag", tagstr);
+		error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
+		free(req.args);
+		if (error != 0) {
+			log_warn("error issuing CTL_PORT_REQ ioctl");
+			return (1);
+		}
+		if (req.status == CTL_LUN_ERROR) {
+			log_warnx("error returned from port removal request: %s",
+			    req.error_str);
+			return (1);
+		}
+		if (req.status != CTL_LUN_OK) {
+			log_warnx("unknown port removal request status %d",
+			    req.status);
+			return (1);
+		}
+	} else {
+		/* Disable LUN mapping. */
+		lm.port = port->p_ctl_port;
+		lm.plun = UINT32_MAX;
+		lm.lun = UINT32_MAX;
+		error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+		if (error != 0)
+			log_warn("CTL_LUN_MAP ioctl failed");
+	}
+	return (0);
+}
+
+#ifdef ICL_KERNEL_PROXY
+void
+kernel_listen(struct addrinfo *ai, bool iser, int portal_id)
+{
+	struct ctl_iscsi req;
+
+	bzero(&req, sizeof(req));
+
+	req.type = CTL_ISCSI_LISTEN;
+	req.data.listen.iser = iser;
+	req.data.listen.domain = ai->ai_family;
+	req.data.listen.socktype = ai->ai_socktype;
+	req.data.listen.protocol = ai->ai_protocol;
+	req.data.listen.addr = ai->ai_addr;
+	req.data.listen.addrlen = ai->ai_addrlen;
+	req.data.listen.portal_id = portal_id;
+
+	if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
+		log_err(1, "error issuing CTL_ISCSI ioctl");
+
+	if (req.status != CTL_ISCSI_OK) {
+		log_errx(1, "error returned from CTL iSCSI listen: %s",
+		    req.error_str);
+	}
+}
+
+void
+kernel_accept(int *connection_id, int *portal_id,
+    struct sockaddr *client_sa, socklen_t *client_salen)
+{
+	struct ctl_iscsi req;
+	struct sockaddr_storage ss;
+
+	bzero(&req, sizeof(req));
+
+	req.type = CTL_ISCSI_ACCEPT;
+	req.data.accept.initiator_addr = (struct sockaddr *)&ss;
+
+	if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
+		log_err(1, "error issuing CTL_ISCSI ioctl");
+
+	if (req.status != CTL_ISCSI_OK) {
+		log_errx(1, "error returned from CTL iSCSI accept: %s",
+		    req.error_str);
+	}
+
+	*connection_id = req.data.accept.connection_id;
+	*portal_id = req.data.accept.portal_id;
+	*client_salen = req.data.accept.initiator_addrlen;
+	memcpy(client_sa, &ss, *client_salen);
+}
+
+void
+kernel_send(struct pdu *pdu)
+{
+	struct ctl_iscsi req;
+
+	bzero(&req, sizeof(req));
+
+	req.type = CTL_ISCSI_SEND;
+	req.data.send.connection_id = pdu->pdu_connection->conn_socket;
+	req.data.send.bhs = pdu->pdu_bhs;
+	req.data.send.data_segment_len = pdu->pdu_data_len;
+	req.data.send.data_segment = pdu->pdu_data;
+
+	if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+		log_err(1, "error issuing CTL_ISCSI ioctl; "
+		    "dropping connection");
+	}
+
+	if (req.status != CTL_ISCSI_OK) {
+		log_errx(1, "error returned from CTL iSCSI send: "
+		    "%s; dropping connection", req.error_str);
+	}
+}
+
+void
+kernel_receive(struct pdu *pdu)
+{
+	struct 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");
+}
+


Property changes on: trunk/usr.sbin/ctld/kernel.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/keys.c
===================================================================
--- trunk/usr.sbin/ctld/keys.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/keys.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,198 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/keys.c 288704 2015-10-05 07:42:05Z mav $");
+
+#include <assert.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)
+		return;
+
+	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);
+}
+
+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);
+}


Property changes on: trunk/usr.sbin/ctld/keys.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/log.c
===================================================================
--- trunk/usr.sbin/ctld/log.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/log.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,199 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/log.c 270888 2014-08-31 20:21:08Z trasz $");
+
+#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);
+}


Property changes on: trunk/usr.sbin/ctld/log.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/login.c
===================================================================
--- trunk/usr.sbin/ctld/login.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/login.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,993 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/login.c 300450 2016-05-23 04:50:01Z truckman $");
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.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;
+	bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
+}
+
+static int
+login_csg(const struct pdu *request)
+{
+	struct iscsi_bhs_login_request *bhslr;
+
+	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+
+	return ((bhslr->bhslr_flags & 0x0C) >> 2);
+}
+
+static void
+login_set_csg(struct pdu *response, int csg)
+{
+	struct iscsi_bhs_login_response *bhslr;
+
+	assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
+	    csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
+	    csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
+
+	bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+
+	bhslr->bhslr_flags &= 0xF3;
+	bhslr->bhslr_flags |= csg << 2;
+}
+
+static struct pdu *
+login_receive(struct connection *conn, bool initial)
+{
+	struct pdu *request;
+	struct iscsi_bhs_login_request *bhslr;
+
+	request = pdu_new(conn);
+	pdu_receive(request);
+	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
+	    ISCSI_BHS_OPCODE_LOGIN_REQUEST) {
+		/*
+		 * The first PDU in session is special - if we receive any PDU
+		 * different than login request, we have to drop the connection
+		 * without sending response ("A target receiving any PDU
+		 * except a Login request before the Login Phase is started MUST
+		 * immediately terminate the connection on which the PDU
+		 * was received.")
+		 */
+		if (initial == false)
+			login_send_error(request, 0x02, 0x0b);
+		log_errx(1, "protocol error: received invalid opcode 0x%x",
+		    request->pdu_bhs->bhs_opcode);
+	}
+	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+	/*
+	 * XXX: Implement the C flag some day.
+	 */
+	if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) {
+		login_send_error(request, 0x03, 0x00);
+		log_errx(1, "received Login PDU with unsupported \"C\" flag");
+	}
+	if (bhslr->bhslr_version_max != 0x00) {
+		login_send_error(request, 0x02, 0x05);
+		log_errx(1, "received Login PDU with unsupported "
+		    "Version-max 0x%x", bhslr->bhslr_version_max);
+	}
+	if (bhslr->bhslr_version_min != 0x00) {
+		login_send_error(request, 0x02, 0x05);
+		log_errx(1, "received Login PDU with unsupported "
+		    "Version-min 0x%x", bhslr->bhslr_version_min);
+	}
+	if (initial == false &&
+	    ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) {
+		login_send_error(request, 0x02, 0x00);
+		log_errx(1, "received Login PDU with decreasing CmdSN: "
+		    "was %u, is %u", conn->conn_cmdsn,
+		    ntohl(bhslr->bhslr_cmdsn));
+	}
+	if (initial == false &&
+	    ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
+		login_send_error(request, 0x02, 0x00);
+		log_errx(1, "received Login PDU with wrong ExpStatSN: "
+		    "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn),
+		    conn->conn_statsn);
+	}
+	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
+
+	return (request);
+}
+
+static struct pdu *
+login_new_response(struct pdu *request)
+{
+	struct pdu *response;
+	struct connection *conn;
+	struct iscsi_bhs_login_request *bhslr;
+	struct iscsi_bhs_login_response *bhslr2;
+
+	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+	conn = request->pdu_connection;
+
+	response = pdu_new_response(request);
+	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+	bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE;
+	login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION);
+	memcpy(bhslr2->bhslr_isid,
+	    bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid));
+	bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
+	bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
+	bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
+	bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
+
+	return (response);
+}
+
+static void
+login_send_error(struct pdu *request, char class, char detail)
+{
+	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 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, struct chap *chap)
+{
+	struct pdu *response;
+	struct keys *response_keys;
+	char *chap_c, *chap_i;
+
+	chap_c = chap_get_challenge(chap);
+	chap_i = chap_get_id(chap);
+
+	response = login_new_response(request);
+	response_keys = keys_new();
+	keys_add(response_keys, "CHAP_A", "5");
+	keys_add(response_keys, "CHAP_I", chap_i);
+	keys_add(response_keys, "CHAP_C", chap_c);
+	free(chap_i);
+	free(chap_c);
+	keys_save(response_keys, response);
+	pdu_send(response);
+	pdu_delete(response);
+	keys_delete(response_keys);
+}
+
+static struct pdu *
+login_receive_chap_r(struct connection *conn, struct auth_group *ag,
+    struct chap *chap, const struct auth **authp)
+{
+	struct pdu *request;
+	struct keys *request_keys;
+	const char *chap_n, *chap_r;
+	const struct auth *auth;
+	int error;
+
+	request = login_receive(conn, false);
+	request_keys = keys_new();
+	keys_load(request_keys, request);
+
+	chap_n = keys_find(request_keys, "CHAP_N");
+	if (chap_n == NULL) {
+		login_send_error(request, 0x02, 0x07);
+		log_errx(1, "received CHAP Login PDU without CHAP_N");
+	}
+	chap_r = keys_find(request_keys, "CHAP_R");
+	if (chap_r == NULL) {
+		login_send_error(request, 0x02, 0x07);
+		log_errx(1, "received CHAP Login PDU without CHAP_R");
+	}
+	error = chap_receive(chap, chap_r);
+	if (error != 0) {
+		login_send_error(request, 0x02, 0x07);
+		log_errx(1, "received CHAP Login PDU with malformed CHAP_R");
+	}
+
+	/*
+	 * Verify the response.
+	 */
+	assert(ag->ag_type == AG_TYPE_CHAP ||
+	    ag->ag_type == AG_TYPE_CHAP_MUTUAL);
+	auth = auth_find(ag, chap_n);
+	if (auth == NULL) {
+		login_send_error(request, 0x02, 0x01);
+		log_errx(1, "received CHAP Login with invalid user \"%s\"",
+		    chap_n);
+	}
+
+	assert(auth->a_secret != NULL);
+	assert(strlen(auth->a_secret) > 0);
+
+	error = chap_authenticate(chap, auth->a_secret);
+	if (error != 0) {
+		login_send_error(request, 0x02, 0x01);
+		log_errx(1, "CHAP authentication failed for user \"%s\"",
+		    auth->a_user);
+	}
+
+	keys_delete(request_keys);
+
+	*authp = auth;
+	return (request);
+}
+
+static void
+login_send_chap_success(struct pdu *request,
+    const struct auth *auth)
+{
+	struct pdu *response;
+	struct keys *request_keys, *response_keys;
+	struct rchap *rchap;
+	const char *chap_i, *chap_c;
+	char *chap_r;
+	int error;
+
+	response = login_new_response(request);
+	login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+
+	/*
+	 * Actually, one more thing: mutual authentication.
+	 */
+	request_keys = keys_new();
+	keys_load(request_keys, request);
+	chap_i = keys_find(request_keys, "CHAP_I");
+	chap_c = keys_find(request_keys, "CHAP_C");
+	if (chap_i != NULL || chap_c != NULL) {
+		if (chap_i == NULL) {
+			login_send_error(request, 0x02, 0x07);
+			log_errx(1, "initiator requested target "
+			    "authentication, but didn't send CHAP_I");
+		}
+		if (chap_c == NULL) {
+			login_send_error(request, 0x02, 0x07);
+			log_errx(1, "initiator requested target "
+			    "authentication, but didn't send CHAP_C");
+		}
+		if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) {
+			login_send_error(request, 0x02, 0x01);
+			log_errx(1, "initiator requests target authentication "
+			    "for user \"%s\", but mutual user/secret "
+			    "is not set", auth->a_user);
+		}
+
+		log_debugx("performing mutual authentication as user \"%s\"",
+		    auth->a_mutual_user);
+
+		rchap = rchap_new(auth->a_mutual_secret);
+		error = rchap_receive(rchap, chap_i, chap_c);
+		if (error != 0) {
+			login_send_error(request, 0x02, 0x07);
+			log_errx(1, "received CHAP Login PDU with malformed "
+			    "CHAP_I or CHAP_C");
+		}
+		chap_r = rchap_get_response(rchap);
+		rchap_delete(rchap);
+		response_keys = keys_new();
+		keys_add(response_keys, "CHAP_N", auth->a_mutual_user);
+		keys_add(response_keys, "CHAP_R", chap_r);
+		free(chap_r);
+		keys_save(response_keys, response);
+		keys_delete(response_keys);
+	} else {
+		log_debugx("initiator did not request target authentication");
+	}
+
+	keys_delete(request_keys);
+	pdu_send(response);
+	pdu_delete(response);
+}
+
+static void
+login_chap(struct connection *conn, struct auth_group *ag)
+{
+	const struct auth *auth;
+	struct chap *chap;
+	struct pdu *request;
+
+	/*
+	 * Receive CHAP_A PDU.
+	 */
+	log_debugx("beginning CHAP authentication; waiting for CHAP_A");
+	request = login_receive_chap_a(conn);
+
+	/*
+	 * Generate the challenge.
+	 */
+	chap = chap_new();
+
+	/*
+	 * Send the challenge.
+	 */
+	log_debugx("sending CHAP_C, binary challenge size is %zd bytes",
+	    sizeof(chap->chap_challenge));
+	login_send_chap_c(request, chap);
+	pdu_delete(request);
+
+	/*
+	 * Receive CHAP_N/CHAP_R PDU and authenticate.
+	 */
+	log_debugx("waiting for CHAP_N/CHAP_R");
+	request = login_receive_chap_r(conn, ag, chap, &auth);
+
+	/*
+	 * 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);
+
+	/*
+	 * Leave username and CHAP information for discovery().
+	 */
+	conn->conn_user = auth->a_user;
+	conn->conn_chap = chap;
+}
+
+static void
+login_negotiate_key(struct pdu *request, const char *name,
+    const char *value, bool skipped_security, struct keys *response_keys)
+{
+	int which, 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 MaxRecvDataSegmentLength "
+			    "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, MAX_DATA_SEGMENT_LENGTH);
+	} 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 if (strcmp(name, "iSCSIProtocolLevel") == 0) {
+		tmp = strtoul(value, NULL, 10);
+		if (tmp > 2)
+			tmp = 2;
+		keys_add_int(response_keys, name, tmp);
+	} else {
+		log_debugx("unknown key \"%s\"; responding "
+		    "with NotUnderstood", name);
+		keys_add(response_keys, name, "NotUnderstood");
+	}
+}
+
+static void
+login_redirect(struct pdu *request, const char *target_address)
+{
+	struct pdu *response;
+	struct iscsi_bhs_login_response *bhslr2;
+	struct keys *response_keys;
+
+	response = login_new_response(request);
+	login_set_csg(response, login_csg(request));
+	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+	bhslr2->bhslr_status_class = 0x01;
+	bhslr2->bhslr_status_detail = 0x01;
+
+	response_keys = keys_new();
+	keys_add(response_keys, "TargetAddress", target_address);
+
+	keys_save(response_keys, response);
+	pdu_send(response);
+	pdu_delete(response);
+	keys_delete(response_keys);
+}
+
+static bool
+login_portal_redirect(struct connection *conn, struct pdu *request)
+{
+	const struct portal_group *pg;
+
+	pg = conn->conn_portal->p_portal_group;
+	if (pg->pg_redirection == NULL)
+		return (false);
+
+	log_debugx("portal-group \"%s\" configured to redirect to %s",
+	    pg->pg_name, pg->pg_redirection);
+	login_redirect(request, pg->pg_redirection);
+
+	return (true);
+}
+
+static bool
+login_target_redirect(struct connection *conn, struct pdu *request)
+{
+	const char *target_address;
+
+	assert(conn->conn_portal->p_portal_group->pg_redirection == NULL);
+
+	if (conn->conn_target == NULL)
+		return (false);
+
+	target_address = conn->conn_target->t_redirection;
+	if (target_address == NULL)
+		return (false);
+
+	log_debugx("target \"%s\" configured to redirect to %s",
+	  conn->conn_target->t_name, target_address);
+	login_redirect(request, target_address);
+
+	return (true);
+}
+
+static void
+login_negotiate(struct connection *conn, struct pdu *request)
+{
+	struct pdu *response;
+	struct iscsi_bhs_login_response *bhslr2;
+	struct keys *request_keys, *response_keys;
+	int i;
+	bool redirected, skipped_security;
+
+	if (request == NULL) {
+		log_debugx("beginning operational parameter negotiation; "
+		    "waiting for Login PDU");
+		request = login_receive(conn, false);
+		skipped_security = false;
+	} else
+		skipped_security = true;
+
+	/*
+	 * RFC 3720, 10.13.5.  Status-Class and Status-Detail, says
+	 * the redirection SHOULD be accepted by the initiator before
+	 * authentication, but MUST be be accepted afterwards; that's
+	 * why we're doing it here and not earlier.
+	 */
+	redirected = login_target_redirect(conn, request);
+	if (redirected) {
+		log_debugx("initiator redirected; exiting");
+		exit(0);
+	}
+
+	request_keys = keys_new();
+	keys_load(request_keys, request);
+
+	response = login_new_response(request);
+	bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+	bhslr2->bhslr_tsih = htons(0xbadd);
+	login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+	login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE);
+	response_keys = keys_new();
+
+	if (skipped_security &&
+	    conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+		if (conn->conn_target->t_alias != NULL)
+			keys_add(response_keys,
+			    "TargetAlias", conn->conn_target->t_alias);
+		keys_add_int(response_keys, "TargetPortalGroupTag",
+		    conn->conn_portal->p_portal_group->pg_tag);
+	}
+
+	for (i = 0; i < KEYS_MAX; i++) {
+		if (request_keys->keys_names[i] == NULL)
+			break;
+
+		login_negotiate_key(request, request_keys->keys_names[i],
+		    request_keys->keys_values[i], skipped_security,
+		    response_keys);
+	}
+
+	log_debugx("operational 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);
+}
+
+static void
+login_wait_transition(struct connection *conn)
+{
+	struct pdu *request, *response;
+	struct iscsi_bhs_login_request *bhslr;
+
+	log_debugx("waiting for state transition request");
+	request = login_receive(conn, false);
+	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+	if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) {
+		login_send_error(request, 0x02, 0x00);
+		log_errx(1, "got no \"T\" flag after answering AuthMethod");
+	}
+
+	log_debugx("got state transition request");
+	response = login_new_response(request);
+	pdu_delete(request);
+	login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+	pdu_send(response);
+	pdu_delete(response);
+
+	login_negotiate(conn, NULL);
+}
+
+void
+login(struct connection *conn)
+{
+	struct pdu *request, *response;
+	struct iscsi_bhs_login_request *bhslr;
+	struct keys *request_keys, *response_keys;
+	struct auth_group *ag;
+	struct portal_group *pg;
+	const char *initiator_name, *initiator_alias, *session_type,
+	    *target_name, *auth_method;
+	bool redirected, fail, trans;
+
+	/*
+	 * Handle the initial Login Request - figure out required authentication
+	 * method and either transition to the next phase, if no authentication
+	 * is required, or call appropriate authentication code.
+	 */
+	log_debugx("beginning Login Phase; waiting for Login PDU");
+	request = login_receive(conn, true);
+	bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+	if (bhslr->bhslr_tsih != 0) {
+		login_send_error(request, 0x02, 0x0a);
+		log_errx(1, "received Login PDU with non-zero TSIH");
+	}
+
+	pg = conn->conn_portal->p_portal_group;
+
+	memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid,
+	    sizeof(conn->conn_initiator_isid));
+
+	/*
+	 * XXX: Implement the C flag some day.
+	 */
+	request_keys = keys_new();
+	keys_load(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);
+	setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name);
+
+	redirected = login_portal_redirect(conn, request);
+	if (redirected) {
+		log_debugx("initiator redirected; exiting");
+		exit(0);
+	}
+
+	initiator_alias = keys_find(request_keys, "InitiatorAlias");
+	if (initiator_alias != NULL)
+		conn->conn_initiator_alias = checked_strdup(initiator_alias);
+
+	assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE);
+	session_type = keys_find(request_keys, "SessionType");
+	if (session_type != NULL) {
+		if (strcmp(session_type, "Normal") == 0) {
+			conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
+		} else if (strcmp(session_type, "Discovery") == 0) {
+			conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY;
+		} else {
+			login_send_error(request, 0x02, 0x00);
+			log_errx(1, "received Login PDU with invalid "
+			    "SessionType \"%s\"", session_type);
+		}
+	} else
+		conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
+
+	assert(conn->conn_target == NULL);
+	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+		target_name = keys_find(request_keys, "TargetName");
+		if (target_name == NULL) {
+			login_send_error(request, 0x02, 0x07);
+			log_errx(1, "received Login PDU without TargetName");
+		}
+
+		conn->conn_port = port_find_in_pg(pg, target_name);
+		if (conn->conn_port == NULL) {
+			login_send_error(request, 0x02, 0x03);
+			log_errx(1, "requested target \"%s\" not found",
+			    target_name);
+		}
+		conn->conn_target = conn->conn_port->p_target;
+	}
+
+	/*
+	 * At this point we know what kind of authentication we need.
+	 */
+	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+		ag = conn->conn_port->p_auth_group;
+		if (ag == NULL)
+			ag = conn->conn_target->t_auth_group;
+		if (ag->ag_name != NULL) {
+			log_debugx("initiator requests to connect "
+			    "to target \"%s\"; auth-group \"%s\"",
+			    conn->conn_target->t_name,
+			    ag->ag_name);
+		} else {
+			log_debugx("initiator requests to connect "
+			    "to target \"%s\"", conn->conn_target->t_name);
+		}
+	} else {
+		assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
+		ag = pg->pg_discovery_auth_group;
+		if (ag->ag_name != NULL) {
+			log_debugx("initiator requests "
+			    "discovery session; auth-group \"%s\"", ag->ag_name);
+		} else {
+			log_debugx("initiator requests discovery session");
+		}
+	}
+
+	if (ag->ag_type == AG_TYPE_DENY) {
+		login_send_error(request, 0x02, 0x01);
+		log_errx(1, "auth-type is \"deny\"");
+	}
+
+	if (ag->ag_type == AG_TYPE_UNKNOWN) {
+		/*
+		 * This can happen with empty auth-group.
+		 */
+		login_send_error(request, 0x02, 0x01);
+		log_errx(1, "auth-type not set, denying access");
+	}
+
+	/*
+	 * Enforce initiator-name and initiator-portal.
+	 */
+	if (auth_name_check(ag, initiator_name) != 0) {
+		login_send_error(request, 0x02, 0x02);
+		log_errx(1, "initiator does not match allowed initiator names");
+	}
+
+	if (auth_portal_check(ag, &conn->conn_initiator_sa) != 0) {
+		login_send_error(request, 0x02, 0x02);
+		log_errx(1, "initiator does not match allowed "
+		    "initiator portals");
+	}
+
+	/*
+	 * Let's see if the initiator intends to do any kind of authentication
+	 * at all.
+	 */
+	if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
+		if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+			login_send_error(request, 0x02, 0x01);
+			log_errx(1, "initiator skipped the authentication, "
+			    "but authentication is required");
+		}
+
+		keys_delete(request_keys);
+
+		log_debugx("initiator skipped the authentication, "
+		    "and we don't need it; proceeding with negotiation");
+		login_negotiate(conn, request);
+		return;
+	}
+
+	fail = false;
+	response = login_new_response(request);
+	response_keys = keys_new();
+	trans = (bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0;
+	auth_method = keys_find(request_keys, "AuthMethod");
+	if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) {
+		log_debugx("authentication not required");
+		if (auth_method == NULL ||
+		    login_list_contains(auth_method, "None")) {
+			keys_add(response_keys, "AuthMethod", "None");
+		} else {
+			log_warnx("initiator requests "
+			    "AuthMethod \"%s\" instead of \"None\"",
+			    auth_method);
+			keys_add(response_keys, "AuthMethod", "Reject");
+		}
+		if (trans)
+			login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+	} else {
+		log_debugx("CHAP authentication required");
+		if (auth_method == NULL ||
+		    login_list_contains(auth_method, "CHAP")) {
+			keys_add(response_keys, "AuthMethod", "CHAP");
+		} else {
+			log_warnx("initiator requests unsupported "
+			    "AuthMethod \"%s\" instead of \"CHAP\"",
+			    auth_method);
+			keys_add(response_keys, "AuthMethod", "Reject");
+			fail = true;
+		}
+	}
+	if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+		if (conn->conn_target->t_alias != NULL)
+			keys_add(response_keys,
+			    "TargetAlias", conn->conn_target->t_alias);
+		keys_add_int(response_keys,
+		    "TargetPortalGroupTag", pg->pg_tag);
+	}
+	keys_save(response_keys, response);
+
+	pdu_send(response);
+	pdu_delete(response);
+	keys_delete(response_keys);
+	pdu_delete(request);
+	keys_delete(request_keys);
+
+	if (fail) {
+		log_debugx("sent reject for AuthMethod; exiting");
+		exit(1);
+	}
+
+	if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+		login_chap(conn, ag);
+		login_negotiate(conn, NULL);
+	} else if (trans) {
+		login_negotiate(conn, NULL);
+	} else {
+		login_wait_transition(conn);
+	}
+}


Property changes on: trunk/usr.sbin/ctld/login.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/parse.y
===================================================================
--- trunk/usr.sbin/ctld/parse.y	                        (rev 0)
+++ trunk/usr.sbin/ctld/parse.y	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,1148 @@
+/* $MidnightBSD$ */
+%{
+/*-
+ * 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: stable/10/usr.sbin/ctld/parse.y 311866 2017-01-10 08:25:03Z mav $
+ */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <stdio.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 AUTH_TYPE BACKEND BLOCKSIZE CHAP CHAP_MUTUAL
+%token CLOSING_BRACKET CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE
+%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER FOREIGN
+%token INITIATOR_NAME INITIATOR_PORTAL ISNS_SERVER ISNS_PERIOD ISNS_TIMEOUT
+%token LISTEN LISTEN_ISER LUN MAXPROC OPENING_BRACKET OPTION
+%token PATH PIDFILE PORT PORTAL_GROUP REDIRECT SEMICOLON SERIAL SIZE STR
+%token TAG TARGET TIMEOUT
+
+%union
+{
+	char *str;
+}
+
+%token <str> STR
+
+%%
+
+statements:
+	|
+	statements statement
+	|
+	statements statement SEMICOLON
+	;
+
+statement:
+	debug
+	|
+	timeout
+	|
+	maxproc
+	|
+	pidfile
+	|
+	isns_server
+	|
+	isns_period
+	|
+	isns_timeout
+	|
+	auth_group
+	|
+	portal_group
+	|
+	lun
+	|
+	target
+	;
+
+debug:		DEBUG STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+			
+		conf->conf_debug = tmp;
+	}
+	;
+
+timeout:	TIMEOUT STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		conf->conf_timeout = tmp;
+	}
+	;
+
+maxproc:	MAXPROC STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		conf->conf_maxproc = tmp;
+	}
+	;
+
+pidfile:	PIDFILE STR
+	{
+		if (conf->conf_pidfile_path != NULL) {
+			log_warnx("pidfile specified more than once");
+			free($2);
+			return (1);
+		}
+		conf->conf_pidfile_path = $2;
+	}
+	;
+
+isns_server:	ISNS_SERVER STR
+	{
+		int error;
+
+		error = isns_new(conf, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+isns_period:	ISNS_PERIOD STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		conf->conf_isns_period = tmp;
+	}
+	;
+
+isns_timeout:	ISNS_TIMEOUT STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		conf->conf_isns_timeout = tmp;
+	}
+	;
+
+auth_group:	AUTH_GROUP auth_group_name
+    OPENING_BRACKET auth_group_entries CLOSING_BRACKET
+	{
+		auth_group = NULL;
+	}
+	;
+
+auth_group_name:	STR
+	{
+		/*
+		 * Make it possible to redefine default
+		 * auth-group. but only once.
+		 */
+		if (strcmp($1, "default") == 0 &&
+		    conf->conf_default_ag_defined == false) {
+			auth_group = auth_group_find(conf, $1);
+			conf->conf_default_ag_defined = true;
+		} else {
+			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_entries auth_group_entry SEMICOLON
+	;
+
+auth_group_entry:
+	auth_group_auth_type
+	|
+	auth_group_chap
+	|
+	auth_group_chap_mutual
+	|
+	auth_group_initiator_name
+	|
+	auth_group_initiator_portal
+	;
+
+auth_group_auth_type:	AUTH_TYPE STR
+	{
+		int error;
+
+		error = auth_group_set_type(auth_group, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+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);
+	}
+	;
+
+auth_group_initiator_name:	INITIATOR_NAME STR
+	{
+		const struct auth_name *an;
+
+		an = auth_name_new(auth_group, $2);
+		free($2);
+		if (an == NULL)
+			return (1);
+	}
+	;
+
+auth_group_initiator_portal:	INITIATOR_PORTAL STR
+	{
+		const struct auth_portal *ap;
+
+		ap = auth_portal_new(auth_group, $2);
+		free($2);
+		if (ap == NULL)
+			return (1);
+	}
+	;
+
+portal_group:	PORTAL_GROUP portal_group_name
+    OPENING_BRACKET portal_group_entries CLOSING_BRACKET
+	{
+		portal_group = NULL;
+	}
+	;
+
+portal_group_name:	STR
+	{
+		/*
+		 * Make it possible to redefine default
+		 * portal-group. but only once.
+		 */
+		if (strcmp($1, "default") == 0 &&
+		    conf->conf_default_pg_defined == false) {
+			portal_group = portal_group_find(conf, $1);
+			conf->conf_default_pg_defined = true;
+		} else {
+			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_entries portal_group_entry SEMICOLON
+	;
+
+portal_group_entry:
+	portal_group_discovery_auth_group
+	|
+	portal_group_discovery_filter
+	|
+	portal_group_foreign
+	|
+	portal_group_listen
+	|
+	portal_group_listen_iser
+	|
+	portal_group_option
+	|
+	portal_group_redirect
+	|
+	portal_group_tag
+	;
+
+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_discovery_filter:	DISCOVERY_FILTER STR
+	{
+		int error;
+
+		error = portal_group_set_filter(portal_group, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+portal_group_foreign:	FOREIGN
+	{
+
+		portal_group->pg_foreign = 1;
+	}
+	;
+
+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);
+	}
+	;
+
+portal_group_option:	OPTION STR STR
+	{
+		struct option *o;
+
+		o = option_new(&portal_group->pg_options, $2, $3);
+		free($2);
+		free($3);
+		if (o == NULL)
+			return (1);
+	}
+	;
+
+portal_group_redirect:	REDIRECT STR
+	{
+		int error;
+
+		error = portal_group_set_redirection(portal_group, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+portal_group_tag:	TAG STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		portal_group->pg_tag = tmp;
+	}
+	;
+
+lun:	LUN lun_name
+    OPENING_BRACKET lun_entries CLOSING_BRACKET
+	{
+		lun = NULL;
+	}
+	;
+
+lun_name:	STR
+	{
+		lun = lun_new(conf, $1);
+		free($1);
+		if (lun == NULL)
+			return (1);
+	}
+	;
+
+target:	TARGET target_name
+    OPENING_BRACKET target_entries CLOSING_BRACKET
+	{
+		target = NULL;
+	}
+	;
+
+target_name:	STR
+	{
+		target = target_new(conf, $1);
+		free($1);
+		if (target == NULL)
+			return (1);
+	}
+	;
+
+target_entries:
+	|
+	target_entries target_entry
+	|
+	target_entries target_entry SEMICOLON
+	;
+
+target_entry:
+	target_alias
+	|
+	target_auth_group
+	|
+	target_auth_type
+	|
+	target_chap
+	|
+	target_chap_mutual
+	|
+	target_initiator_name
+	|
+	target_initiator_portal
+	|
+	target_portal_group
+	|
+	target_port
+	|
+	target_redirect
+	|
+	target_lun
+	|
+	target_lun_ref
+	;
+
+target_alias:	ALIAS STR
+	{
+		if (target->t_alias != NULL) {
+			log_warnx("alias for target \"%s\" "
+			    "specified more than once", target->t_name);
+			return (1);
+		}
+		target->t_alias = $2;
+	}
+	;
+
+target_auth_group:	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_name);
+			else
+				log_warnx("cannot use both auth-group and explicit "
+				    "authorisations for target \"%s\"",
+				    target->t_name);
+			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_name);
+			return (1);
+		}
+		free($2);
+	}
+	;
+
+target_auth_type:	AUTH_TYPE STR
+	{
+		int error;
+
+		if (target->t_auth_group != NULL) {
+			if (target->t_auth_group->ag_name != NULL) {
+				log_warnx("cannot use both auth-group and "
+				    "auth-type for target \"%s\"",
+				    target->t_name);
+				return (1);
+			}
+		} else {
+			target->t_auth_group = auth_group_new(conf, NULL);
+			if (target->t_auth_group == NULL) {
+				free($2);
+				return (1);
+			}
+			target->t_auth_group->ag_target = target;
+		}
+		error = auth_group_set_type(target->t_auth_group, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+target_chap:	CHAP STR STR
+	{
+		const struct auth *ca;
+
+		if (target->t_auth_group != NULL) {
+			if (target->t_auth_group->ag_name != NULL) {
+				log_warnx("cannot use both auth-group and "
+				    "chap for target \"%s\"",
+				    target->t_name);
+				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);
+	}
+	;
+
+target_chap_mutual:	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 use both auth-group and "
+				    "chap-mutual for target \"%s\"",
+				    target->t_name);
+				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);
+	}
+	;
+
+target_initiator_name:	INITIATOR_NAME STR
+	{
+		const struct auth_name *an;
+
+		if (target->t_auth_group != NULL) {
+			if (target->t_auth_group->ag_name != NULL) {
+				log_warnx("cannot use both auth-group and "
+				    "initiator-name for target \"%s\"",
+				    target->t_name);
+				free($2);
+				return (1);
+			}
+		} else {
+			target->t_auth_group = auth_group_new(conf, NULL);
+			if (target->t_auth_group == NULL) {
+				free($2);
+				return (1);
+			}
+			target->t_auth_group->ag_target = target;
+		}
+		an = auth_name_new(target->t_auth_group, $2);
+		free($2);
+		if (an == NULL)
+			return (1);
+	}
+	;
+
+target_initiator_portal:	INITIATOR_PORTAL STR
+	{
+		const struct auth_portal *ap;
+
+		if (target->t_auth_group != NULL) {
+			if (target->t_auth_group->ag_name != NULL) {
+				log_warnx("cannot use both auth-group and "
+				    "initiator-portal for target \"%s\"",
+				    target->t_name);
+				free($2);
+				return (1);
+			}
+		} else {
+			target->t_auth_group = auth_group_new(conf, NULL);
+			if (target->t_auth_group == NULL) {
+				free($2);
+				return (1);
+			}
+			target->t_auth_group->ag_target = target;
+		}
+		ap = auth_portal_new(target->t_auth_group, $2);
+		free($2);
+		if (ap == NULL)
+			return (1);
+	}
+	;
+
+target_portal_group:	PORTAL_GROUP STR STR
+	{
+		struct portal_group *tpg;
+		struct auth_group *tag;
+		struct port *tp;
+
+		tpg = portal_group_find(conf, $2);
+		if (tpg == NULL) {
+			log_warnx("unknown portal-group \"%s\" for target "
+			    "\"%s\"", $2, target->t_name);
+			free($2);
+			free($3);
+			return (1);
+		}
+		tag = auth_group_find(conf, $3);
+		if (tag == NULL) {
+			log_warnx("unknown auth-group \"%s\" for target "
+			    "\"%s\"", $3, target->t_name);
+			free($2);
+			free($3);
+			return (1);
+		}
+		tp = port_new(conf, target, tpg);
+		if (tp == NULL) {
+			log_warnx("can't link portal-group \"%s\" to target "
+			    "\"%s\"", $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		tp->p_auth_group = tag;
+		free($2);
+		free($3);
+	}
+	|		PORTAL_GROUP STR
+	{
+		struct portal_group *tpg;
+		struct port *tp;
+
+		tpg = portal_group_find(conf, $2);
+		if (tpg == NULL) {
+			log_warnx("unknown portal-group \"%s\" for target "
+			    "\"%s\"", $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		tp = port_new(conf, target, tpg);
+		if (tp == NULL) {
+			log_warnx("can't link portal-group \"%s\" to target "
+			    "\"%s\"", $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		free($2);
+	}
+	;
+
+target_port:	PORT STR
+	{
+		struct pport *pp;
+		struct port *tp;
+
+		pp = pport_find(conf, $2);
+		if (pp == NULL) {
+			log_warnx("unknown port \"%s\" for target \"%s\"",
+			    $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		if (!TAILQ_EMPTY(&pp->pp_ports)) {
+			log_warnx("can't link port \"%s\" to target \"%s\", "
+			    "port already linked to some target",
+			    $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		tp = port_new_pp(conf, target, pp);
+		if (tp == NULL) {
+			log_warnx("can't link port \"%s\" to target \"%s\"",
+			    $2, target->t_name);
+			free($2);
+			return (1);
+		}
+		free($2);
+	}
+	;
+
+target_redirect:	REDIRECT STR
+	{
+		int error;
+
+		error = target_set_redirection(target, $2);
+		free($2);
+		if (error != 0)
+			return (1);
+	}
+	;
+
+target_lun:	LUN lun_number
+    OPENING_BRACKET lun_entries CLOSING_BRACKET
+	{
+		lun = NULL;
+	}
+	;
+
+lun_number:	STR
+	{
+		uint64_t tmp;
+		int ret;
+		char *name;
+
+		if (expand_number($1, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($1);
+			return (1);
+		}
+		if (tmp >= MAX_LUNS) {
+			yyerror("LU number is too big");
+			free($1);
+			return (1);
+		}
+
+		ret = asprintf(&name, "%s,lun,%ju", target->t_name, tmp);
+		if (ret <= 0)
+			log_err(1, "asprintf");
+		lun = lun_new(conf, name);
+		if (lun == NULL)
+			return (1);
+
+		lun_set_scsiname(lun, name);
+		target->t_luns[tmp] = lun;
+	}
+	;
+
+target_lun_ref:	LUN STR STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			free($3);
+			return (1);
+		}
+		free($2);
+		if (tmp >= MAX_LUNS) {
+			yyerror("LU number is too big");
+			free($3);
+			return (1);
+		}
+
+		lun = lun_find(conf, $3);
+		free($3);
+		if (lun == NULL)
+			return (1);
+
+		target->t_luns[tmp] = lun;
+	}
+	;
+
+lun_entries:
+	|
+	lun_entries lun_entry
+	|
+	lun_entries lun_entry SEMICOLON
+	;
+
+lun_entry:
+	lun_backend
+	|
+	lun_blocksize
+	|
+	lun_device_id
+	|
+	lun_device_type
+	|
+	lun_ctl_lun
+	|
+	lun_option
+	|
+	lun_path
+	|
+	lun_serial
+	|
+	lun_size
+	;
+
+lun_backend:	BACKEND STR
+	{
+		if (lun->l_backend != NULL) {
+			log_warnx("backend for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			free($2);
+			return (1);
+		}
+		lun_set_backend(lun, $2);
+		free($2);
+	}
+	;
+
+lun_blocksize:	BLOCKSIZE STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		if (lun->l_blocksize != 0) {
+			log_warnx("blocksize for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			return (1);
+		}
+		lun_set_blocksize(lun, tmp);
+	}
+	;
+
+lun_device_id:	DEVICE_ID STR
+	{
+		if (lun->l_device_id != NULL) {
+			log_warnx("device_id for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			free($2);
+			return (1);
+		}
+		lun_set_device_id(lun, $2);
+		free($2);
+	}
+	;
+
+lun_device_type:	DEVICE_TYPE STR
+	{
+		uint64_t tmp;
+
+		if (strcasecmp($2, "disk") == 0 ||
+		    strcasecmp($2, "direct") == 0)
+			tmp = 0;
+		else if (strcasecmp($2, "processor") == 0)
+			tmp = 3;
+		else if (strcasecmp($2, "cd") == 0 ||
+		    strcasecmp($2, "cdrom") == 0 ||
+		    strcasecmp($2, "dvd") == 0 ||
+		    strcasecmp($2, "dvdrom") == 0)
+			tmp = 5;
+		else if (expand_number($2, &tmp) != 0 ||
+		    tmp > 15) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		lun_set_device_type(lun, tmp);
+	}
+	;
+
+lun_ctl_lun:	CTL_LUN STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		if (lun->l_ctl_lun >= 0) {
+			log_warnx("ctl_lun for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			return (1);
+		}
+		lun_set_ctl_lun(lun, tmp);
+	}
+	;
+
+lun_option:	OPTION STR STR
+	{
+		struct option *o;
+
+		o = option_new(&lun->l_options, $2, $3);
+		free($2);
+		free($3);
+		if (o == NULL)
+			return (1);
+	}
+	;
+
+lun_path:	PATH STR
+	{
+		if (lun->l_path != NULL) {
+			log_warnx("path for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			free($2);
+			return (1);
+		}
+		lun_set_path(lun, $2);
+		free($2);
+	}
+	;
+
+lun_serial:	SERIAL STR
+	{
+		if (lun->l_serial != NULL) {
+			log_warnx("serial for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			free($2);
+			return (1);
+		}
+		lun_set_serial(lun, $2);
+		free($2);
+	}
+	;
+
+lun_size:	SIZE STR
+	{
+		uint64_t tmp;
+
+		if (expand_number($2, &tmp) != 0) {
+			yyerror("invalid numeric value");
+			free($2);
+			return (1);
+		}
+
+		if (lun->l_size != 0) {
+			log_warnx("size for lun \"%s\" "
+			    "specified more than once",
+			    lun->l_name);
+			return (1);
+		}
+		lun_set_size(lun, tmp);
+	}
+	;
+%%
+
+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 conf *oldconf)
+{
+	struct auth_group *ag;
+	struct portal_group *pg;
+	struct pport *pp;
+	int error;
+
+	log_debugx("obtaining configuration from %s", path);
+
+	conf = conf_new();
+
+	TAILQ_FOREACH(pp, &oldconf->conf_pports, pp_next)
+		pport_copy(pp, conf);
+
+	ag = auth_group_new(conf, "default");
+	assert(ag != NULL);
+
+	ag = auth_group_new(conf, "no-authentication");
+	assert(ag != NULL);
+	ag->ag_type = AG_TYPE_NO_AUTHENTICATION;
+
+	ag = auth_group_new(conf, "no-access");
+	assert(ag != NULL);
+	ag->ag_type = AG_TYPE_DENY;
+
+	pg = portal_group_new(conf, "default");
+	assert(pg != NULL);
+
+	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 = 1;
+	yyrestart(yyin);
+	error = yyparse();
+	auth_group = NULL;
+	portal_group = NULL;
+	target = NULL;
+	lun = NULL;
+	fclose(yyin);
+	if (error != 0) {
+		conf_delete(conf);
+		return (NULL);
+	}
+
+	if (conf->conf_default_ag_defined == false) {
+		log_debugx("auth-group \"default\" not defined; "
+		    "going with defaults");
+		ag = auth_group_find(conf, "default");
+		assert(ag != NULL);
+		ag->ag_type = AG_TYPE_DENY;
+	}
+
+	if (conf->conf_default_pg_defined == false) {
+		log_debugx("portal-group \"default\" not defined; "
+		    "going with defaults");
+		pg = portal_group_find(conf, "default");
+		assert(pg != NULL);
+		portal_group_add_listen(pg, "0.0.0.0:3260", false);
+		portal_group_add_listen(pg, "[::]:3260", false);
+	}
+
+	conf->conf_kernel_port_on = true;
+
+	error = conf_verify(conf);
+	if (error != 0) {
+		conf_delete(conf);
+		return (NULL);
+	}
+
+	return (conf);
+}


Property changes on: trunk/usr.sbin/ctld/parse.y
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: trunk/usr.sbin/ctld/pdu.c
===================================================================
--- trunk/usr.sbin/ctld/pdu.c	                        (rev 0)
+++ trunk/usr.sbin/ctld/pdu.c	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,265 @@
+/* $MidnightBSD$ */
+/*-
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/pdu.c 288704 2015-10-05 07:42:05Z mav $");
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ctld.h"
+#include "iscsi_proto.h"
+
+#ifdef ICL_KERNEL_PROXY
+#include <sys/ioctl.h>
+#endif
+
+extern bool proxy_mode;
+
+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
+
+static void
+pdu_receive_proxy(struct pdu *pdu)
+{
+	size_t len;
+
+	assert(proxy_mode);
+
+	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;
+}
+
+static void
+pdu_send_proxy(struct pdu *pdu)
+{
+
+	assert(proxy_mode);
+
+	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+	kernel_send(pdu);
+}
+
+#endif /* ICL_KERNEL_PROXY */
+
+static 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];
+
+#ifdef ICL_KERNEL_PROXY
+	if (proxy_mode)
+		return (pdu_receive_proxy(pdu));
+#endif
+
+	assert(proxy_mode == false);
+
+	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;
+
+#ifdef ICL_KERNEL_PROXY
+	if (proxy_mode)
+		return (pdu_send_proxy(pdu));
+#endif
+
+	assert(proxy_mode == false);
+
+	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");
+}
+
+void
+pdu_delete(struct pdu *pdu)
+{
+
+	free(pdu->pdu_data);
+	free(pdu->pdu_bhs);
+	free(pdu);
+}


Property changes on: trunk/usr.sbin/ctld/pdu.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: trunk/usr.sbin/ctld/token.l
===================================================================
--- trunk/usr.sbin/ctld/token.l	                        (rev 0)
+++ trunk/usr.sbin/ctld/token.l	2018-06-10 20:15:35 UTC (rev 10818)
@@ -0,0 +1,96 @@
+/* $MidnightBSD$ */
+%{
+/*-
+ * 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: stable/10/usr.sbin/ctld/token.l 288810 2015-10-05 11:30:18Z mav $
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.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; }
+auth-type		{ return AUTH_TYPE; }
+backend			{ return BACKEND; }
+blocksize		{ return BLOCKSIZE; }
+chap			{ return CHAP; }
+chap-mutual		{ return CHAP_MUTUAL; }
+ctl-lun			{ return CTL_LUN; }
+debug			{ return DEBUG; }
+device-id		{ return DEVICE_ID; }
+device-type		{ return DEVICE_TYPE; }
+discovery-auth-group	{ return DISCOVERY_AUTH_GROUP; }
+discovery-filter	{ return DISCOVERY_FILTER; }
+foreign			{ return FOREIGN; }
+initiator-name		{ return INITIATOR_NAME; }
+initiator-portal	{ return INITIATOR_PORTAL; }
+listen			{ return LISTEN; }
+listen-iser		{ return LISTEN_ISER; }
+lun			{ return LUN; }
+maxproc			{ return MAXPROC; }
+option			{ return OPTION; }
+path			{ return PATH; }
+pidfile			{ return PIDFILE; }
+isns-server		{ return ISNS_SERVER; }
+isns-period		{ return ISNS_PERIOD; }
+isns-timeout		{ return ISNS_TIMEOUT; }
+port			{ return PORT; }
+portal-group		{ return PORTAL_GROUP; }
+redirect		{ return REDIRECT; }
+serial			{ return SERIAL; }
+size			{ return SIZE; }
+tag			{ return TAG; }
+target			{ return TARGET; }
+timeout			{ return TIMEOUT; }
+\"[^"]+\"		{ yylval.str = strndup(yytext + 1,
+			    strlen(yytext) - 2); return STR; }
+[a-zA-Z0-9\.\- at _/\:\[\]]+ { yylval.str = strdup(yytext); return STR; }
+\{			{ return OPENING_BRACKET; }
+\}			{ return CLOSING_BRACKET; }
+#.*$			/* ignore comments */;
+\r\n			{ lineno++; }
+\n			{ lineno++; }
+;			{ return SEMICOLON; }
+[ \t]+			/* ignore whitespace */;
+.			{ yylval.str = strdup(yytext); return STR; }
+%%


Property changes on: trunk/usr.sbin/ctld/token.l
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+MidnightBSD=%H
\ No newline at end of property


More information about the Midnightbsd-cvs mailing list