/*
 *  Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org>
 *  Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
 *  Copyright (C) 2007 - 2018 Western Digital Corporation
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 */

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include "iscsid.h"

#define BUFSIZE		4096
#define CONFIG_FILE	"/etc/iscsi-scstd.conf"

/*
 * Index must match ISCSI_USER_DIR_* and ISCSI_USER_DIR_OUTGOING must
 * be the last to confirm expectations of __config_account_add()!!
 */
struct iscsi_key user_keys[] = {
	{ .name = "IncomingUser",},
	{ .name = "OutgoingUser",},
	{ .name = NULL,},
};

static struct __qelem discovery_users_in = LIST_HEAD_INIT(discovery_users_in);
static struct __qelem discovery_users_out = LIST_HEAD_INIT(discovery_users_out);

struct iscsi_attr *iscsi_attr_lookup_by_sysfs_name(
	struct __qelem *attrs_list, const char *sysfs_name)
{
	struct iscsi_attr *attr;

	list_for_each_entry(attr, attrs_list, ulist) {
		if (!strcmp(attr->sysfs_name, sysfs_name))
			return attr;
	}

	return NULL;
}

struct iscsi_attr *iscsi_attr_lookup_by_key(
	struct __qelem *attrs_list, const char *key)
{
	struct iscsi_attr *attr;

	list_for_each_entry(attr, attrs_list, ulist) {
		if (!strcmp(attr->attr_key, key))
			return attr;
	}

	return NULL;
}

static void __iscsi_attr_destroy(struct iscsi_attr *attr, int del)
{
	if (!attr)
		return;

	if (del)
		list_del(&attr->ulist);

	free((void *)attr->attr_key);
	free((void *)attr->attr_value);
	free(attr);
	return;
}

void iscsi_attr_destroy(struct iscsi_attr *attr)
{
	__iscsi_attr_destroy(attr, 1);
}

void iscsi_attrs_free(struct __qelem *attrs_list)
{
	struct iscsi_attr *attr, *t;

	list_for_each_entry_safe(attr, t, attrs_list, ulist) {
		iscsi_attr_destroy(attr);
	}

	return;
}

int iscsi_attr_create(int attr_size, struct __qelem *attrs_list,
	const char *sysfs_name_tmpl, const char *key, const char *val,
	u32 mode, struct iscsi_attr **res_attr)
{
	int res = 0;
	struct iscsi_attr *attr;
	int num = 0;

	if (iscsi_attr_lookup_by_key(attrs_list, key) != NULL) {
		log_error("Attribute %s already exists", key);
		res = -EINVAL;
		goto out;
	}

	attr = malloc(attr_size);
	if (attr == NULL)
		goto out;

	memset(attr, 0, attr_size);

	attr->attr_key = strdup(key);
	if (attr->attr_key == NULL) {
		log_error("Unable to duplicate attr_key %s", key);
		res = -ENOMEM;
		goto out_free;
	}

	if ((val != NULL) && (*val != '\0')) {
		attr->attr_value = strdup(val);
		if (attr->attr_value == NULL) {
			log_error("Unable to duplicate attr_value %s", val);
			res = -ENOMEM;
			goto out_free;
		}
	}

	attr->sysfs_mode = mode;

	if (sysfs_name_tmpl == NULL)
		goto add;

	if (sysfs_name_tmpl != NULL) {
		strlcpy(attr->sysfs_name, sysfs_name_tmpl, sizeof(attr->sysfs_name));
		if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL)
			goto add;
	}

	while (1) {
		if (num == 0)
			num++;
		snprintf(attr->sysfs_name, sizeof(attr->sysfs_name),
			"%s%d", sysfs_name_tmpl, num);
		if (iscsi_attr_lookup_by_sysfs_name(attrs_list, attr->sysfs_name) == NULL)
			break;
		num++;
	}

add:
	list_add_tail(attr, attrs_list);

	*res_attr = attr;

out:
	return res;

out_free:
	__iscsi_attr_destroy(attr, 0);
	goto out;
}

int iscsi_attr_replace(struct __qelem *attrs_list, const char *sysfs_name,
	char *raw_value)
{
	int res = 0;
	struct iscsi_attr *attr, *a;
	char *key, *val, *new_key, *new_val;

	key = config_sep_string(&raw_value);
	val = config_sep_string(&raw_value);

	attr = iscsi_attr_lookup_by_sysfs_name(attrs_list, sysfs_name);
	if (attr == NULL) {
		log_error("Unknown parameter %s\n", sysfs_name);
		res = -EINVAL;
		goto out;
	}

	a = iscsi_attr_lookup_by_key(attrs_list, key);

	if ((a != NULL) && (a != attr)) {
		log_error("Attr %s (sysfs_name %s) already exists\n", key,
			a->sysfs_name);
		res = -EEXIST;
		goto out;
	}

	new_key = strdup(key);
	if (new_key == NULL) {
		log_error("Unable to duplicate attr_key %s", key);
		res = -ENOMEM;
		goto out;
	}

	if ((val != NULL) && (*val != '\0')) {
		new_val = strdup(val);
		if (new_val == NULL) {
			log_error("Unable to duplicate attr_value %s", val);
			res = -ENOMEM;
			free(new_key);
			goto out;
		}
	} else
		new_val = NULL;

	free((void *)attr->attr_key);
	attr->attr_key = new_key;

	free((void *)attr->attr_value);
	attr->attr_value = new_val;

out:
	return res;
}

static struct __qelem *account_list_get(struct target *target, int dir)
{
	struct __qelem *list = NULL;

	if (target != NULL) {
		list = (dir == ISCSI_USER_DIR_INCOMING) ?
			&target->target_in_accounts : &target->target_out_accounts;
	} else
		list = (dir == ISCSI_USER_DIR_INCOMING) ?
			&discovery_users_in : &discovery_users_out;

	return list;
}

char *config_sep_string(char **pp)
{
	char *p;
	char *q;
	static char blank = '\0';

	if ((pp == NULL) || (*pp == NULL))
		return &blank;

	for (p = *pp; isspace(*p) || (*p == '='); p++)
		;

	for (q = p; (*q != '\0') && !isspace(*q) && (*q != '='); q++)
		;

	if (*q != '\0')
		*q++ = '\0';

	*pp = q;
	return p;
}

/*
 * Strip leading and trailing whitespace.
 *
 * Modifies the contents of the parameter string.
 */
char *config_strip_string(char *s)
{
	size_t size;
	char *end;

	size = strlen(s);

	if (!size)
		return s;

	end = s + size - 1;
	while (end >= s && isspace(*end))
		end--;
	*(end + 1) = '\0';

	while (*s && isspace(*s))
		s++;

	return s;
}

static char *config_gets(char *buf, int size, const char *data, int *offset)
{
	int offs = *offset, i = 0;

	while ((i < size-1) && (data[offs] != '\n') && (data[offs] != ';') && (data[offs] != '\0'))
		buf[i++] = data[offs++];

	if ((i == 0) && (data[offs] == '\0'))
		return NULL;

	if (data[offs] != '\0')
		offs++;

	*offset = offs;
	buf[i] = '\0';

	return buf;
}

int accounts_empty(u32 tid, int dir)
{
	struct target *target;
	struct __qelem *list;

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL)
			return 0;
	} else
		target = NULL;

	list = account_list_get(target, dir);

	return list_empty(list);
}

static struct iscsi_attr *__account_lookup_by_name(struct target *target,
	int dir, const char *name)
{
	struct __qelem *list;

	list = account_list_get(target, dir);

	return iscsi_attr_lookup_by_key(list, name);
}

static struct iscsi_attr *account_lookup_by_name(u32 tid, int dir, const char *name)
{
	struct target *target;

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL)
			return NULL;
	} else
		target = NULL;

	return __account_lookup_by_name(target, dir, name);
}

struct iscsi_attr *account_get_first(u32 tid, int dir)
{
	struct target *target;
	struct __qelem *list;
	struct iscsi_attr *user;

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL)
			return NULL;
	} else
		target = NULL;

	list = account_list_get(target, dir);

	list_for_each_entry(user, list, ulist) {
		return user;
	}

	return NULL;
}

struct iscsi_attr *account_lookup_by_sysfs_name(struct target *target,
	int dir, const char *sysfs_name)
{
	struct __qelem *list;

	list = account_list_get(target, dir);

	return iscsi_attr_lookup_by_sysfs_name(list, sysfs_name);
}

int config_account_query(u32 tid, int dir, const char *name, char *pass)
{
	struct iscsi_attr *user;

	if (!(user = account_lookup_by_name(tid, dir, name)))
		return -ENOENT;

	strlcpy(pass, ISCSI_USER_PASS(user), ISCSI_NAME_LEN);

	return 0;
}

int config_account_list(u32 tid, int dir, u32 *cnt, u32 *overflow,
	char *buf, size_t buf_sz)
{
	struct target *target;
	struct __qelem *list;
	struct iscsi_attr *user;

	*cnt = *overflow = 0;

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL)
			return -ENOENT;
	} else
		target = NULL;

	list = account_list_get(target, dir);

	if (!list)
		return -ENOENT;

	list_for_each_entry(user, list, ulist) {
		if (buf_sz >= ISCSI_NAME_LEN) {
			strlcpy(buf, ISCSI_USER_NAME(user), ISCSI_NAME_LEN);
			buf_sz -= ISCSI_NAME_LEN;
			buf += ISCSI_NAME_LEN;
			*cnt += 1;
		} else
			*overflow += 1;
	}

	return 0;
}

static void account_destroy(struct iscsi_attr *user)
{
	iscsi_attr_destroy(user);
	return;
}

void accounts_free(struct __qelem *accounts_list)
{
	iscsi_attrs_free(accounts_list);
	return;
}

int config_account_del(u32 tid, int dir, char *name, u32 cookie)
{
	struct iscsi_attr *user;
	int res = 0;
	struct target *target;

	if (!name) {
		log_error("%s", "Name expected");
		res = -EINVAL;
		goto out;
	}

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL) {
			log_error("Target %d not found", tid);
			res = -ESRCH;
			goto out;
		}
	} else
		target = NULL;

	user = __account_lookup_by_name(target, dir, name);
	if (user == NULL) {
		log_error("User %s not found", name);
		res = -ENOENT;
		goto out;
	}

	log_debug(1, "Deleting %s user %s (%p, target %s, sysfs name %s)",
		(dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming",
		ISCSI_USER_NAME(user), user, target ? target->name : "discovery",
		user->sysfs_name);

	res = kernel_user_del(target, user, cookie);
	if (res != 0)
		goto out;

	account_destroy(user);

out:
	return res;
}

static int account_create(struct __qelem *list, const char *sysfs_name,
	const char *name, const char *pass, struct iscsi_attr **res_user)
{
	return iscsi_attr_create(sizeof(struct iscsi_attr), list, sysfs_name,
			name, pass, 0600, res_user);
}

int account_replace(struct target *target, int direction,
	const char *sysfs_name, char *value)
{
	struct __qelem *list;

	list = account_list_get(target, direction);

	return iscsi_attr_replace(list, sysfs_name, value);
}

int __config_account_add(struct target *target, int dir, char *name, char *pass,
			 const char *sysfs_name, int send_to_kern, u32 cookie)
{
	int err = 0;
	struct iscsi_attr *user;
	struct __qelem *list;

	if (!name || !pass || (*name == '\0') || (*pass == '\0')) {
		log_error("%s", "Name or password is NULL");
		err = -EINVAL;
		goto out;
	}

	/* Check for minimum RFC defined value */
	if (strlen(pass) < 12) {
		log_error("Secret for user %s is too short. At least 12 bytes "
			"are required\n", name);
		err = -EINVAL;
		goto out;
	}

	if (dir > ISCSI_USER_DIR_OUTGOING) {
		log_error("Wrong direction %d\n", dir);
		err = -EINVAL;
		goto out;
	}

	if (sysfs_name == NULL)
		sysfs_name = (dir == ISCSI_USER_DIR_OUTGOING) ?
				user_keys[ISCSI_USER_DIR_OUTGOING].name :
				user_keys[ISCSI_USER_DIR_INCOMING].name;

	list = account_list_get(target, dir);

	if (dir == ISCSI_USER_DIR_OUTGOING) {
		struct iscsi_attr *old;

		list_for_each_entry(old, list, ulist) {
			log_warning("Only one outgoing %s account is "
				"supported. Replacing the old one.\n",
				target ? "target" : "discovery");
			account_destroy(old);
			break;
		}
	}

	err = account_create(list, sysfs_name, name, pass, &user);
	if (err != 0)
		goto out;

	if (send_to_kern && (sysfs_name != NULL)) {
		err = kernel_user_add(target, user, cookie);
		if (err != 0)
			goto out_destroy;
	}

	log_debug(1, "User %s (%p, sysfs name %s) added to target %s "
		"(direction %s)", ISCSI_USER_NAME(user), user, user->sysfs_name,
		target ? target->name : "discovery",
		(dir == ISCSI_USER_DIR_OUTGOING) ? "outgoing" : "incoming");

out:
	return err;

out_destroy:
	account_destroy(user);
	goto out;
}

int config_account_add(u32 tid, int dir, char *name, char *pass,
		       const char *sysfs_name, u32 cookie)
{
	int err = 0;
	struct target *target;

	if (tid) {
		target = target_find_by_id(tid);
		if (target == NULL) {
			err = -ENOENT;
			goto out;
		}
	} else
		target = NULL;

	err = __config_account_add(target, dir, name, pass, sysfs_name, 1, cookie);

out:
	return err;
}

/*
 * Access control code
 */

typedef union {
	struct sockaddr		sa;
	struct sockaddr_in	sa_in;
	struct sockaddr_in6	sa_in6;
} sockaddress;

static int netmask_match_v6(const struct sockaddr_in6 *sa1,
			    const struct sockaddr_in6 *sa2, uint32_t mbit)
{
	uint16_t mask, a1[8], a2[8];
	int i;

	for (i = 0; i < 8; i++) {
		a1[i] = ntohs(sa1->sin6_addr.s6_addr16[i]);
		a2[i] = ntohs(sa2->sin6_addr.s6_addr16[i]);
	}

	for (i = 0; i < mbit / 16; i++)
		if (a1[i] ^ a2[i])
			return 0;

	if (mbit % 16) {
		mask = ~((1 << (16 - (mbit % 16))) - 1);
		if ((mask & a1[mbit / 16]) ^ (mask & a2[mbit / 16]))
			return 0;
	}

	return 1;
}

static int netmask_match_v4(const struct sockaddr_in *sa1,
			    const struct sockaddr_in *sa2,
			    uint32_t mbit)
{
	uint32_t s1, s2, mask = ~((1 << (32 - mbit)) - 1);

	s1 = htonl(sa1->sin_addr.s_addr);
	s2 = htonl(sa2->sin_addr.s_addr);

	if (~mask & s1)
		return 0;

	if (!((mask & s2) ^ (mask & s1)))
		return 1;

	return 0;
}

static int netmask_match(const sockaddress *sa1, const sockaddress *sa2,
			 char *buf)
{
	unsigned long mbit;
	uint8_t family = sa1->sa.sa_family;

	mbit = strtoul(buf, NULL, 0);
	if (mbit == ULONG_MAX ||
	    (family == AF_INET && mbit > 31) ||
	    (family == AF_INET6 && mbit > 127))
		return 0;

	if (family == AF_INET)
		return netmask_match_v4(&sa1->sa_in, &sa2->sa_in, mbit);

	return netmask_match_v6(&sa1->sa_in6, &sa2->sa_in6, mbit);
}

static int address_match(const sockaddress *sa1, const sockaddress *sa2)
{
	if (sa1->sa.sa_family == AF_INET)
		return sa1->sa_in.sin_addr.s_addr ==
		       sa2->sa_in.sin_addr.s_addr;
	else {
		const struct in6_addr *a1, *a2;

		a1 = &sa1->sa_in6.sin6_addr;
		a2 = &sa2->sa_in6.sin6_addr;

		return (a1->s6_addr32[0] == a2->s6_addr32[0] &&
			a1->s6_addr32[1] == a2->s6_addr32[1] &&
			a1->s6_addr32[2] == a2->s6_addr32[2] &&
			a1->s6_addr32[3] == a2->s6_addr32[3]);
	}

	return 0;
}

static int __initiator_match(int fd, char *str)
{
	sockaddress from;
	socklen_t len;
	char *p, *q;
	int err = 0;

	len = sizeof(from);
	if (getpeername(fd, (struct sockaddr *) &from, &len) < 0)
		return 0;

	while ((p = strsep(&str, ","))) {
		struct addrinfo hints, *res;

		while (isblank(*p))
			p++;

		if (!strcmp(p, "ALL"))
			return 1;

		if (*p == '[') {
			p++;
			if (!(q = strchr(p, ']')))
				return 0;
			*(q++) = '\0';
		} else
			q = p;

		if ((q = strchr(q, '/')))
			*(q++) = '\0';

		memset(&hints, 0, sizeof(hints));
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_NUMERICHOST;

		if (getaddrinfo(p, NULL, &hints, &res) < 0)
			return 0;

		if (q)
			err = netmask_match((sockaddress *)res->ai_addr, &from, q);
		else
			err = address_match((sockaddress *)res->ai_addr, &from);

		freeaddrinfo(res);

		if (err)
			break;
	}

	return err;
}

static int initiator_match(u32 tid, int fd, const char *filename)
{
	int err = 0;
	FILE *fp;
	char buf[BUFSIZE], *p;

	if (!(fp = fopen(filename, "r")))
		return err;

	/*
	 * Every time we are called, we read the file. So we don't need to
	 * implement the 'reload feature'. It's slow, but that doesn't matter.
	 */
	while ((p = fgets(buf, sizeof(buf), fp))) {
		if (!p || *p == '#')
			continue;

		p = &buf[strlen(buf) - 1];
		if (*p != '\n')
			continue;
		*p = '\0';

		p = buf;
		while (!isblank(*p) && (*p != '\0'))
			p++;
		if (*p == '\0')
			continue;

		*p = '\0';
		p++;

		if (target_find_id_by_name(buf) != tid && strcmp(buf, "ALL"))
			continue;

		err = __initiator_match(fd, p);
		break;
	}

	fclose(fp);
	return err;
}

int config_initiator_access_allowed(u32 tid, int fd)
{
	if (initiator_match(tid, fd, "/etc/initiators.deny") &&
	    !initiator_match(tid, fd, "/etc/initiators.allow"))
		return 0;
	else
		return 1;
}

/*
 * Main configuration code
 */

int config_target_create(u32 *tid, char *name)
{
	int err;
	struct target *target;

	err = target_create(name, &target);
	if (err != 0)
		goto out;

	err = target_add(target, tid, 0);
	if (err != 0)
		goto out_free;

out:
	return err;

out_free:
	target_free(target);
	goto out;
}

int config_target_destroy(u32 tid)
{
	int err;

	if ((err = target_del(tid, 0)) < 0)
		return err;

	return err;
}

int config_params_get(u32 tid, u64 sid, int type, struct iscsi_param *params)
{
	int err, i;
	struct target *target;

	if (sid != 0) {
		err = kernel_params_get(tid, sid, type, params);
		goto out;
	}

	err = 0;

	target = target_find_by_id(tid);
	if (target == NULL) {
		log_error("target %d not found", tid);
		err = -EINVAL;
		goto out;
	}

	if (type == key_session) {
		for (i = 0; i < session_key_last; i++)
			params[i].val = target->session_params[i];
	} else {
		for (i = 0; i < target_key_last; i++)
			params[i].val = target->target_params[i];
	}

out:
	return err;
}

int config_params_set(u32 tid, u64 sid, int type, u32 partial,
	struct iscsi_param *params)
{
	int err, i;
	struct target *target;

	if (sid != 0) {
		err = kernel_params_set(tid, sid, type, partial, params);
		goto out;
	}

	err = 0;

	target = target_find_by_id(tid);
	if (target == 0) {
		log_error("target %d not found", tid);
		err = -EINVAL;
		goto out;
	}

	if (partial == 0)
		partial = (typeof(partial))-1;

	if (type == key_session) {
		for (i = 0; i < session_key_last; i++) {
			uint32_t in_val = params[i].val;
			if (partial & (1 << i)) {
				err = params_check_val(session_keys, i, &params[i].val);
				if (err < 0) {
					log_error("%s: Wrong value %u->%u for session parameter %s\n",
						__func__, in_val, params[i].val, session_keys[i].name);
					goto out;
				}
			}
		}
		for (i = 0; i < session_key_last; i++) {
			if (partial & (1 << i))
				target->session_params[i] = params[i].val;
		}
	} else {
		for (i = 0; i < target_key_last; i++) {
			uint32_t in_val = params[i].val;
			if (partial & (1 << i)) {
				err = params_check_val(target_keys, i, &params[i].val);
				if (err < 0) {
					log_error("%s: Wrong value %u->%u for target parameter %s\n",
						__func__, in_val, params[i].val, target_keys[i].name);
					goto out;
				}
			}
		}
		for (i = 0; i < target_key_last; i++) {
			if (partial & (1 << i))
				target->target_params[i] = params[i].val;
		}
	}

out:
	return err;
}

int config_parse_main(const char *data, u32 cookie)
{
	char buf[BUFSIZE];
	char *p, *q, *n;
	int idx, offset = 0;
	u32 val;
	int res = 0;
	struct target *target = NULL;
	int global_section = 1; /* supposed to be bool and true */
	int parsed_something = 0; /* supposed to be bool and false */
	int stop_on_errors = (cookie != 0);

	while (config_gets(buf, sizeof(buf), data, &offset)) {
		parsed_something = 1;
		/*
		 * If stop_on_errors is false, let's always continue parsing
		 * and only report errors.
		 */
		if (stop_on_errors && (res != 0))
			goto out_target_free;

		q = buf;
		p = config_sep_string(&q);
		if ((*p == '#') || (*p == '\0'))
			continue;

		if (!strcasecmp(p, "Target")) {
			global_section = 0;

			if (target != NULL) {
				res = target_add(target, NULL, cookie);
				if (res != 0)
					target_free(target);
			}

			target = NULL;
			p = config_sep_string(&q);
			if (*p == '\0') {
				log_error("Target name required on %s\n", q);
				continue;
			}

			n = config_sep_string(&q);
			if (*n != '\0') {
				log_error("Unexpected parameter value %s\n", n);
				res = -EINVAL;
				continue;
			}

			log_debug(1, "Creating target %s", p);
			res = target_create(p, &target);
			if (res != 0)
				goto out;
		} else if (!strcasecmp(p, "Alias") && target) {
			;
		} else if (!((idx = params_index_by_name(p, target_keys)) < 0) && (target != NULL)) {
			char *str = config_sep_string(&q);

			n = config_sep_string(&q);
			if (*n != '\0') {
				log_error("Unexpected parameter value %s\n", n);
				res = -EINVAL;
				continue;
			}

			res = params_str_to_val(target_keys, idx, str, &val);
			if (res < 0) {
				log_error("Wrong value %s for parameter %s\n",
					str, target_keys[idx].name);
				continue;
			}

			uint32_t in_val = val;
			res = params_check_val(target_keys, idx, &val);
			if (res < 0) {
				log_error("%s: Wrong value %u->%u for target parameter %s\n",
					  __func__, in_val, val, target_keys[idx].name);
				continue;
			}
			target->target_params[idx] = val;
		} else if (!((idx = params_index_by_name(p, session_keys)) < 0) && (target != NULL)) {
			char *str = config_sep_string(&q);

			n = config_sep_string(&q);
			if (*n != '\0') {
				log_error("Unexpected parameter value %s\n", n);
				res = -EINVAL;
				continue;
			}

			res = params_str_to_val(session_keys, idx, str, &val);
			if (res < 0) {
				log_error("Wrong value %s for parameter %s\n",
					str, session_keys[idx].name);
				continue;
			}

			uint32_t in_val = val;
			res = params_check_val(session_keys, idx, &val);
			if (res < 0) {
				log_error("%s: Wrong value %u->%u for session parameter %s\n",
					  __func__, in_val, val, session_keys[idx].name);
				continue;
			}
			target->session_params[idx] = val;
		} else if (!((idx = params_index_by_name_numwild(p, user_keys)) < 0) &&
			   ((target != NULL) || global_section)) {
			char *name, *pass;

			name = config_sep_string(&q);
			pass = config_sep_string(&q);

			n = config_sep_string(&q);
			if (*n != '\0') {
				log_error("Unexpected parameter value %s\n", n);
				res = -EINVAL;
				continue;
			}

			res = __config_account_add(target, idx, name, pass, p,
					(target == 0), 0);
			if (res < 0)
				continue;
		} else if (global_section &&
			   (!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME) ||
			    !strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME)))
			continue;
		else {
			log_error("Unknown or unexpected param: %s\n", p);
			res = -EINVAL;
			continue;
		}
	}

	if (stop_on_errors && (res != 0))
		goto out_target_free;

	if (target != NULL) {
		res = target_add(target, NULL, cookie);
		if (res != 0)
			goto out_target_free;
	}

out:
	if (stop_on_errors) {
		if ((res == 0) && !parsed_something)
			res = -ENOENT;
	} else
		res = 0;

	return res;

out_target_free:
	if (target != NULL)
		target_free(target);
	goto out;
}

static int config_isns_load(const char *config)
{
	char buf[BUFSIZE];
	int offset = 0;
	char *p, *q;

	while (config_gets(buf, sizeof(buf), config, &offset)) {
		q = buf;
		p = config_sep_string(&q);
		if ((*p == '\0') || (*p == '#'))
			continue;
		if (!strcasecmp(p, ISCSI_ISNS_SERVER_ATTR_NAME)) {
			free(isns_server);
			isns_server = strdup(config_sep_string(&q));
		} else if (!strcasecmp(p, ISCSI_ISNS_ACCESS_CONTROL_ATTR_NAME)) {
			char *str = config_sep_string(&q);

			if (!strcasecmp(str, "No"))
				isns_access_control = 0;
			else
				isns_access_control = 1;
		}
	}

	return 0;
}

int config_load(const char *config_name)
{
	int i, err = 0, rc;
	int config;
	const char *cname;
	int size;
	char *buf;

	if (config_name != NULL)
		cname = config_name;
	else
		cname = CONFIG_FILE;

	config = open(cname, O_RDONLY);
	if (config == -1) {
		if ((errno == ENOENT) && (config_name == NULL)) {
			goto out;
		} else {
			err = -errno;
			log_error("Open config file %s failed: %s", cname,
				strerror(errno));
			goto out;
		}
	}

	size = lseek(config, 0, SEEK_END);
	if (size < 0) {
		err = -errno;
		log_error("lseek() failed: %s", strerror(errno));
		goto out_close;
	}

	buf = malloc(size+1);
	if (buf == NULL) {
		err = -ENOMEM;
		log_error("malloc() failed: %s", strerror(-err));
		goto out_close;
	}

	rc = lseek(config, 0, SEEK_SET);
	if (rc < 0) {
		err = -errno;
		log_error("lseek() failed: %s", strerror(errno));
		goto out_free;
	}

	i = 0;
	do {
		rc = read(config, &buf[i], size - i);
		if (rc < 0) {
			err = -errno;
			log_error("read() failed: %s", strerror(errno));
			goto out_free;
		} else if (rc == 0)
			break;
		i += rc;
	} while (i < size);

	size = i;
	buf[size] = '\0';

	err = config_isns_load(buf);
	if ((err == 0) && (isns_server != NULL)) {
		rc = isns_init();

		if (rc != 0) {
			log_error("iSNS server %s init failed: %d", isns_server, rc);
			isns_exit();
		}
	}

	config_parse_main(buf, 0);

out_free:
	free(buf);

out_close:
	close(config);

out:
	return err;
}
