/*	$NetBSD: iscsic_parse.c,v 1.5 2023/11/25 08:06:02 mlelstv Exp $	*/

/*-
 * Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Wasabi Systems, Inc.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 "iscsic_globals.h"

#include <ctype.h>
#include <assert.h>

/*
 * get_address:
 *    Get an address specification that may include port and group tag.
 *
 *    Parameter:
 *       portal   The portal address
 *       str      The parameter string to scan
 *
 *    Aborts app on error.
 */

STATIC void
get_address(iscsi_portal_address_t * portal, char *str, char *arg)
{
	char *sp;
	int val;

	if (!str || !*str)
		arg_error(arg, "Address is missing");

	/* Parse and strip trailing group tag */
	sp = strrchr(str, ',');
	if (sp != NULL) {
		if (sscanf(sp + 1, "%d", &val) != 1)
			arg_error(arg, "Bad address format: Expected group tag");
		if (val < 0 || val > 0xffff)
			arg_error(arg, "Bad address format: Group tag out of range");
		portal->group_tag = (uint16_t) val;
		*sp = '\0';
	}

	/* Skip over bracketed IPv6 address */
	sp = strchr(str, ']');
	if (sp != NULL)
		sp++;
	else
		sp = str;

	/* Parse and strip trailing port number */
	sp = strchr(sp, ':');
	if (sp != NULL) {
		if (strchr(sp + 1, ':') != NULL) {
			/*
			 *  If there's a second colon, assume
			 *  it's an unbracketed IPv6 address
			 */
			portal->port = 0;
		} else {
			if (sscanf(sp + 1, "%d", &val) != 1)
				arg_error(arg, "Bad address format: Expected port number");
			if (val < 0 || val > 0xffff)
				arg_error(arg, "Bad address format: Port number ut  of range");
			portal->port = (uint16_t) val;
			*sp = '\0';
		}
	}

	/* Remove brackets */
	if (*str == '[') {
		sp = strchr(str, ']');
		if (sp != NULL && !*(sp+1)) {
			str = str + 1;
			*sp = '\0';
		}
	}

	/*
	 * only check length, don't verify correct format
	 * (too many possibilities)
	 */
	if (strlen(str) >= sizeof(portal->address))
		arg_error(arg, "Bad address format: Address string too long");

	strlcpy((char *)portal->address, str, sizeof(portal->address));
}



/*
 * get_short_int:
 *    Get a short integer.
 *
 *    Parameter:
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *       name     The argument name
 *
 *    Returns given integer, aborts app on error.
 */

STATIC uint16_t
get_short_int(char *sp, char *arg, const char *name)
{
	int val;

	if (!sp || !*sp)
		arg_error(arg, "%s is missing", name);

	if (!sscanf(sp, "%d", &val))
		arg_error(arg, "Expected integer %s", name);
	if (val < 0 || val > 0xffff)
		arg_error(arg, "%s out of range", name);

	return (uint16_t) val;
}


/*
 * get_dsl:
 *    Get MaxRecvDataSegmentLength
 *
 *    Parameter:
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *
 *    Returns given integer, aborts app on error.
 */

STATIC uint32_t
get_dsl(char *sp, char *arg)
{
	int val;

	if (!sp || !*sp)
		arg_error(arg, "Missing MaxRecvDataSegmentLength");
	if (!sscanf(sp, "%d", &val))
		arg_error(arg, "Integer MaxRecvDataSegmentLength expected");
	if (val < 512 || val > 0xffffff)
		arg_error(arg, "MaxRecvDataSegmentLength out of range");

	return (uint32_t) val;
}


/*
 * get_str:
 *    Get a string.
 *
 *    Parameter:
 *       dest     The destination string
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *       name     The argument name
 *
 *    Aborts app on error.
 */

STATIC void
get_str(char *dest, char *sp, char *arg, const char *name)
{

	if (!sp || !*sp)
		arg_error(arg, "%s is missing", name);
	if (strlen(sp) >= ISCSI_STRING_LENGTH)
		arg_error(arg, "%s is too long", name);

	strlcpy(dest, sp, ISCSI_STRING_LENGTH);
}

/*
 * cl_get_target:
 *    Get a target address specification that may include name, address, port,
 *    and group tag, with address/port/tag possibly repeated.
 *
 *    Parameter:
 *       ptarg       pointer to hold the resulting add target request parameter
 *       argc, argv  program parameters (shifted)
 *       nreq        target name is required if TRUE
 *
 *    Returns:    0 if there is no target, else the size of the allocated
 *                  request.
 *                Aborts app on bad parameter or mem allocation error.
 */

int
cl_get_target(iscsid_add_target_req_t ** ptarg, int argc, char **argv, int nreq)
{
	iscsid_add_target_req_t *targ;
	char *sp;
	size_t num, len, name;
	int i, p;

	/* count number of addresses first, so we know how much memory to allocate */
	for (i = (int)(num = name = 0); i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;
		if (argv[i][1] == 'a')
			num++;
		if (argv[i][1] == 'n')
			name++;
	}

	if (!name && nreq)
		return 0;

	len = sizeof(iscsid_add_target_req_t) +
		num * sizeof(iscsi_portal_address_t);

	if (NULL == (targ = calloc(1, len)))
		gen_error("Can't allocate %zu bytes of memory", len);

	*ptarg = targ;
	p = -1;

	for (i = 0; i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

		switch (argv[i][1]) {
		case 'n':				/* target name */
			get_str((char *)targ->TargetName, sp, argv[i], "Target name");
			break;

		case 'a':				/* target address */
			get_address(&targ->portal[++p], sp, argv[i]);
			break;

		case 'p':				/* port */
			assert(p >= 0);
			targ->portal[p].port = get_short_int(sp, argv[i], "Port");
			break;

		case 'g':				/* group tag */
			assert(p >= 0);
			targ->portal[p].group_tag = get_short_int(sp, argv[i],
														"Group tag");
			break;

		default:
			continue;
		}
		if (!argv[i][2])
			argv[i + 1] = NULL;

		argv[i] = NULL;
	}
	targ->num_portals = p + 1;

	return (int)len;
}


/*
 * cl_get_isns:
 *    Get an iSNS server address specification that may include name, address
 *    and port.
 *
 *    Parameter:
 *       srv         add_isns_server request parameter
 *       argc, argv  program parameters (shifted)
 *
 *    Returns:    0 on error, 1 if OK.
 */

int
cl_get_isns(iscsid_add_isns_server_req_t * srv, int argc, char **argv)
{
	iscsi_portal_address_t addr;
	char *sp;
	int i, found;

	(void) memset(&addr, 0x0, sizeof(addr));
	found = FALSE;

	for (i = 0; i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

		switch (argv[i][1]) {
		case 'N':				/* symbolic name */
			get_str((char *)srv->name, sp, argv[i], "Server name");
			break;

		case 'a':				/* target address */
			get_address(&addr, sp, argv[i]);
			found = TRUE;
			break;

		case 'p':				/* port */
			addr.port = get_short_int(sp, argv[i], "Port");
			break;

		default:
			continue;
		}
		if (!argv[i][2]) {
			argv[i + 1] = NULL;
		}
		argv[i] = NULL;
	}

	strlcpy((char *)srv->address, (char *)addr.address, sizeof(srv->address));
	srv->port = addr.port;

	return found;
}


/*
 * cl_get_auth_opts:
 *    Get authentication options.
 *
 *    Parameter:
 *          auth        authentication parameters
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there are no authorization options, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_auth_opts(iscsid_set_target_authentication_req_t *auth,
				 int argc, char **argv)
{
	int n, i, found;
	char *sp;

	found = FALSE;
	memset(auth, 0, sizeof(*auth));

	for (i = 0; i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-') {
			continue;
		}
		sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

		switch (argv[i][1]) {
		case 't':				/* authentication type */
			if (!sp || !*sp)
				arg_error(argv[i], "Missing authentication type");
			n = 0;
			while (*sp) {
				switch (*sp) {
				case 'n':		/* no authentication */
					auth->auth_info.auth_type[n] = ISCSI_AUTH_None;
					break;
				case 'c':		/* CHAP authentication */
					auth->auth_info.auth_type[n] = ISCSI_AUTH_CHAP;
					break;
				case 'C':		/* Mutual CHAP authentication */
					auth->auth_info.auth_type[n] = ISCSI_AUTH_CHAP;
					auth->auth_info.mutual_auth = 1;
					break;
				default:
					arg_error(argv[i], "Bad authentication type '%c'", *sp);
				}
				sp++;
				n++;
			}
			auth->auth_info.auth_number = n;
			break;

		case 'u':				/* user name */
			get_str((char *)auth->user_name, sp, argv[i], "User name");
			break;

		case 's':				/* secret */
			get_str((char *)auth->password, sp, argv[i], "Secret");
			break;

		case 'S':				/* target secret */
			get_str((char *)auth->target_password, sp, argv[i], "Target secret");
			break;

		default:
			continue;
		}
		if (!argv[i][2])
			argv[i + 1] = NULL;

		argv[i] = NULL;
		found = TRUE;
	}
	return found;
}


/*
 * cl_get_target_opts:
 *    Get session/connection options.
 *
 *    Parameter:
 *          opt         target options
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there are no target options, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_target_opts(iscsid_get_set_target_options_t * opt, int argc, char **argv)
{
	int i, found;
	char *sp;

	found = FALSE;
	memset(opt, 0, sizeof(*opt));

	for (i = 0; i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

		switch (argv[i][1]) {
		case 'h':				/* Header Digest */
			opt->HeaderDigest = ISCSI_DIGEST_CRC32C;
			opt->is_present.HeaderDigest = 1;
			break;

		case 'd':				/* Data Digest */
			opt->DataDigest = ISCSI_DIGEST_CRC32C;
			opt->is_present.DataDigest = 1;
			break;

		case 'w':				/* Time 2 Wait */
			opt->DefaultTime2Wait = get_short_int(sp, argv[i], "Time to wait");
			opt->is_present.DefaultTime2Wait = 1;
			if (!argv[i][2])
				argv[i + 1] = NULL;
			break;

		case 'r':				/* Time 2 Retain */
			opt->DefaultTime2Retain = get_short_int(sp, argv[i],
													"Time to retain");
			opt->is_present.DefaultTime2Retain = 1;
			if (!argv[i][2])
				argv[i + 1] = NULL;
			break;

		case 'e':				/* Error Recovery Level */
			opt->ErrorRecoveryLevel = get_short_int(sp, argv[i],
													"ErrorRecoveryLevel");
			opt->is_present.ErrorRecoveryLevel = 1;
			if (!argv[i][2])
				argv[i + 1] = NULL;
			break;

		case 'l':				/* Data Segment Length */
			opt->MaxRecvDataSegmentLength = get_dsl(sp, argv[i]);
			opt->is_present.MaxRecvDataSegmentLength = 1;
			if (!argv[i][2])
				argv[i + 1] = NULL;
			break;

		default:
			continue;
		}
		argv[i] = NULL;
		found = TRUE;
	}
	return found;
}


/*
 * cl_get_portal:
 *    Get a portal address specification that may include address, port,
 *    and group tag, plus portal options.
 *
 *    Parameter:
 *          port        add portal request parameter
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    FALSE if there is no portal, else TRUE.
 *                Aborts app on bad parameter or mem allocation error.
 */

int
cl_get_portal(iscsid_add_portal_req_t * port, int argc, char **argv)
{
	char *sp;
	int i, found;
	iscsid_portal_options_t *opt = &port->options;

	found = FALSE;
	memset(port, 0, sizeof(*port));

	for (i = 0; i < argc; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

		switch (argv[i][1]) {
		case 'a':				/* target address */
			get_address(&port->portal, sp, argv[i]);
			found = TRUE;
			break;

		case 'p':				/* port */
			port->portal.port = get_short_int(sp, argv[i], "Port");
			break;

		case 'g':				/* group tag */
			port->portal.group_tag = get_short_int(sp, argv[i], "Group tag");
			break;

		case 'h':				/* Header Digest */
			opt->HeaderDigest = ISCSI_DIGEST_CRC32C;
			opt->is_present.HeaderDigest = 1;
			break;

		case 'd':				/* Data Digest */
			opt->DataDigest = ISCSI_DIGEST_CRC32C;
			opt->is_present.DataDigest = 1;
			break;

		case 'l':				/* Data Segment Length */
			opt->MaxRecvDataSegmentLength = get_dsl(sp, argv[i]);
			opt->is_present.MaxRecvDataSegmentLength = 1;
			if (!argv[i][2])
				argv[i + 1] = NULL;
			break;

		default:
			continue;
		}
		if (!argv[i][2])
			argv[i + 1] = NULL;

		argv[i] = NULL;
	}
	return found;
}


/*
 * cl_get_id:
 *    Get an identifier (symbolic or numeric)
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          sid         the ID
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no ID, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_id(char ident, iscsid_sym_id_t * sid, int argc, char **argv)
{
	int i, found;
	char *sp;

	found = FALSE;
	memset(sid, 0, sizeof(*sid));

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2] :
				((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Missing ID");
			if (strlen(sp) >= ISCSI_STRING_LENGTH)
				arg_error(argv[i], "ID String too long");
			if (!sscanf(sp, "%d", &sid->id))
				strlcpy((char *)sid->name, sp, sizeof(sid->name));
			else if (!sid->id)
				arg_error(argv[i], "Invalid ID");

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}
	return found;
}


/*
 * cl_get_symname:
 *    Get a symbolic name
 *
 *    Parameter:
 *          sn          the name
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no symbolic name, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_symname(uint8_t * sn, int argc, char **argv)
{
	int i, found;
	char *sp;

	found = FALSE;
	*sn = '\0';

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == 'N') {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Symbolic name missing");
			if (isdigit((unsigned char)*sp))
				arg_error(argv[i], "Symbolic name must not be numeric");
			if (strlen(sp) >= ISCSI_STRING_LENGTH)
				arg_error(argv[i], "Symbolic name too long");

			strlcpy((char *)sn, sp, ISCSI_STRING_LENGTH);

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}
	return found;
}


/*
 * cl_get_string:
 *    Get a string value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          pstr        the result string
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no string, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_string(char ident, char *pstr, int argc, char **argv)
{
	int i, found;
	char *sp;

	found = FALSE;
	*pstr = '\0';

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			get_str(pstr, sp, argv[i], "String");

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}
	return found;
}


/*
 * cl_get_opt:
 *    Get an option with no value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if the option was not found, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_opt(char ident, int argc, char **argv)
{
	int i, found;

	found = FALSE;

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			argv[i] = NULL;
			found = TRUE;
		}
	}
	return found;
}


/*
 * cl_get_char:
 *    Get an option with a character value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option character (0 if not found).
 *                Aborts app on bad parameter.
 */

char
cl_get_char(char ident, int argc, char **argv)
{
	int i, found;
	char *sp;
	char ch = 0;

	found = FALSE;

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Option character missing");
			if (strlen(sp) > 1)
				arg_error(argv[i], "Option invalid");
			ch = *sp;

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}

	return ch;
}


/*
 * cl_get_int:
 *    Get an option with an integer value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (0 if not found).
 *                Aborts app on bad parameter.
 */

int
cl_get_int(char ident, int argc, char **argv)
{
	int i, found;
	char *sp;
	int val = 0;

	found = FALSE;

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Option value missing");
			if (!sscanf(sp, "%i", &val))
				arg_error(argv[i], "Integer expected");

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}

	return val;
}


/*
 * cl_get_uint:
 *    Get an option with a positive integer value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (-1 if not found).
 *                Aborts app on bad parameter.
 */

#if 0
int
cl_get_uint(char ident, int argc, char **argv)
{
	int i, found;
	char *sp;
	int val = -1;

	found = FALSE;

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Option value missing");
			if (!sscanf(sp, "%i", &val))
				arg_error(argv[i], "Positive integer expected");

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}

	return val;
}
#endif


/*
 * cl_get_longlong:
 *    Get an option with a 64-bit value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (0 if not found).
 *                Aborts app on bad parameter.
 */

uint64_t
cl_get_longlong(char ident, int argc, char **argv)
{
	int i, found;
	char *sp;
	uint64_t val = 0;

	found = FALSE;

	for (i = 0; i < argc && !found; i++) {
		if (!argv[i] || argv[i][0] != '-')
			continue;

		if (argv[i][1] == ident) {
			sp = (argv[i][2]) ? &argv[i][2]
				: ((i + 1 < argc) ? argv[i + 1] : NULL);

			if (!sp || !*sp)
				arg_error(argv[i], "Option value missing");
			if (!sscanf(sp, "%qi", (long long *)(void *)&val))
				arg_error(argv[i], "Integer expected");

			if (!argv[i][2])
				argv[i + 1] = NULL;

			argv[i] = NULL;
			found = TRUE;
		}
	}

	return val;
}
