/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * nwamadm is a command interpreter to administer NWAM profiles.  It
 * is all in C (i.e., no lex/yacc), and all the argument passing is
 * argc/argv based.  main() calls the command's handler function,
 * which first calls parse_argv() to parse the input arguments and set
 * approriate variables for each command.  The rest of the program is
 * helper functions for the handler functions.
 */

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <libdlwlan.h>
#include <libnwam.h>
#include <libscf.h>
#include <locale.h>
#include <netinet/in.h>
#include <ofmt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#if !defined(TEXT_DOMAIN)		/* should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it wasn't */
#endif

typedef void (cmd_func_t)(int, char **);

struct cmd {
	uint_t		cmd_num;		/* command number */
	const char	*cmd_name;		/* command name */
	cmd_func_t	*cmd_handler;		/* function to call */
	const char	*cmd_usage;		/* short form help */
	const char	*cmd_desc;		/* command description */
	boolean_t	cmd_needs_nwamd;	/* nwam needs to run */
};

/* constants for commands */
#define	CMD_HELP	0
#define	CMD_ENABLE	1
#define	CMD_DISABLE	2
#define	CMD_LIST	3
#define	CMD_SHOW_EVENTS	4
#define	CMD_SCAN_WIFI	5
#define	CMD_SELECT_WIFI	6

#define	CMD_MIN		CMD_HELP
#define	CMD_MAX		CMD_SELECT_WIFI

/* functions to call */
static cmd_func_t help_func, enable_func, disable_func, list_func;
static cmd_func_t show_events_func, scan_wifi_func, select_wifi_func;
static ofmt_cb_t print_list_cb;

/* table of commands and usage */
static struct cmd cmdtab[] = {
	{ CMD_HELP,		"help",		help_func,
	    "help",
	    "Print this usage message.",		B_FALSE		},
	{ CMD_ENABLE,		"enable",	enable_func,
	    "enable [-p <profile-type>] [-c <ncu-class>] <object-name>",
	    "Enable the specified profile.",		B_FALSE		},
	{ CMD_DISABLE,		"disable",	disable_func,
	    "disable [-p <profile-type>] [-c <ncu-class>] <object-name>",
	    "Disable the specified profile.",		B_FALSE		},
	{ CMD_LIST,		"list",		list_func,
	    "list [-x] [-p <profile-type>] [-c <ncu-class>] [<object-name>]",
	    "List profiles and their current states.",	B_TRUE		},
	{ CMD_SHOW_EVENTS,	"show-events",	show_events_func,
	    "show-events",
	    "Display all events.",			B_TRUE		},
	{ CMD_SCAN_WIFI,	"scan-wifi",	scan_wifi_func,
	    "scan-wifi <link-name>",
	    "Request a WiFi scan for the selected link.", B_TRUE	},
	{ CMD_SELECT_WIFI,	"select-wifi",	select_wifi_func,
	    "select-wifi <link-name>",
	    "Make a WLAN selection from the last WiFi scan.", B_TRUE	}
};

/* Structure for "nwamadm list" output */

typedef struct profile_entry {
	nwam_object_type_t	p_type;
	nwam_ncu_class_t	p_ncu_class;
	char			p_name[NWAM_MAX_NAME_LEN];
	nwam_state_t		p_state;
	nwam_aux_state_t	p_aux_state;
} profile_entry_t;

/* widths of colums for printing */
#define	TYPE_WIDTH		12	/* width of TYPE column */
#define	PROFILE_WIDTH		15	/* width of PROFILE column */
#define	STATE_WIDTH		15	/* width of STATE column */
#define	AUXSTATE_WIDTH		36	/* width of AUXILIARY STATE column */

#define	EVENT_WIDTH		22	/* width of EVENT column */
#define	DESCRIPTION_WIDTH	56	/* width of DESCRIPTION column */

/* id for columns of "nwamadm list" */
typedef enum {
	LIST_TYPE,
	LIST_PROFILE,
	LIST_STATE,
	LIST_AUXSTATE
} list_field_id_t;

static const ofmt_field_t list_fields[] = {
	/* header,		width,		id,		callback */
	{ "TYPE",		TYPE_WIDTH,	LIST_TYPE,	print_list_cb },
	{ "PROFILE",		PROFILE_WIDTH,	LIST_PROFILE,	print_list_cb },
	{ "STATE",		STATE_WIDTH,	LIST_STATE,	print_list_cb },
	{ "AUXILIARY STATE",	AUXSTATE_WIDTH,	LIST_AUXSTATE,	print_list_cb },
	{ NULL,			0,		0,		NULL }
};

/* Global variables */

/* set early in main(), never modified thereafter, used all over the place */
static char *execname;

/* whether the auxilary states are to be printed or not */
static boolean_t extended_list = B_FALSE;

/* Functions */

static const char *
cmd_to_str(int cmd_num)
{
	assert(cmd_num >= CMD_MIN && cmd_num <= CMD_MAX);
	return (cmdtab[cmd_num].cmd_name);
}

/* returns description of given command */
static const char *
long_help(int cmd_num)
{
	assert(cmd_num >= CMD_MIN && cmd_num <= CMD_MAX);
	return (gettext(cmdtab[cmd_num].cmd_desc));
}

/*
 * Called with explicit B_TRUE when help is explicitly required,
 * B_FALSE for errors
 */
static void
usage(boolean_t explicit)
{
	int	i;
	FILE	*fd = explicit ? stdout : stderr;

	(void) fprintf(fd, gettext("usage: <subcommand> <args> ...\n"));
	for (i = CMD_MIN; i <= CMD_MAX; i++) {
		(void) fprintf(fd, "\t%s\n", cmdtab[i].cmd_usage);
		if (explicit)
			(void) fprintf(fd, "\t\t%s\n\n", long_help(i));
	}
}

/* PRINTFLIKE1 */
static void
die(const char *format, ...)
{
	va_list alist;

	format = gettext(format);
	(void) fprintf(stderr, "%s: ", execname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);
	(void) fprintf(stderr, "\n");

	exit(EXIT_FAILURE);
}

/* PRINTFLIKE2 */
static void
die_nwamerr(nwam_error_t err, const char *format, ...)
{
	va_list alist;

	format = gettext(format);
	(void) fprintf(stderr, "%s: ", execname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);
	(void) fprintf(stderr, ": %s\n", nwam_strerror(err));

	exit(EXIT_FAILURE);
}

/* prints the usage for cmd_num and exits */
static void
die_usage(int cmd_num)
{
	assert(cmd_num >= CMD_MIN && cmd_num <= CMD_MAX);

	(void) fprintf(stderr, "%s: %s\n", gettext("usage"),
	    cmdtab[cmd_num].cmd_usage);
	(void) fprintf(stderr, "\t%s\n", long_help(cmd_num));

	exit(EXIT_FAILURE);
}

/*
 * Prints the usage and description of all commands
 */
/* ARGSUSED */
static void
help_func(int argc, char *argv[])
{
	usage(B_TRUE);
}

/* determines if the NCP is active or not.  If so, sets arg and halts walk. */
static int
active_ncp_callback(nwam_ncp_handle_t ncph, void *arg)
{
	char			**namep = arg;
	nwam_state_t		state = NWAM_STATE_UNINITIALIZED;
	nwam_aux_state_t	aux;

	(void) nwam_ncp_get_state(ncph, &state, &aux);
	if (state == NWAM_STATE_ONLINE) {
		if (nwam_ncp_get_name(ncph, namep) != NWAM_SUCCESS)
			*namep = NULL;
		return (1);
	}

	return (0);
}

/* find the currently active NCP and returns its handle */
static nwam_ncp_handle_t
determine_active_ncp()
{
	char *active_ncp;
	nwam_ncp_handle_t ncph;
	nwam_error_t ret;

	if (nwam_walk_ncps(active_ncp_callback, &active_ncp, 0, NULL)
	    == NWAM_WALK_HALTED) {
		if (active_ncp == NULL)
			return (NULL);

		/* retrieve the NCP handle */
		ret = nwam_ncp_read(active_ncp, 0, &ncph);
		free(active_ncp);
		if (ret == NWAM_SUCCESS)
			return (ncph);
	}

	return (NULL);
}

/* check if the given name is a valid loc, test by reading the given loc */
static boolean_t
valid_loc(const char *name)
{
	nwam_loc_handle_t loch;

	if (nwam_loc_read(name, 0, &loch) != NWAM_SUCCESS)
		return (B_FALSE);
	nwam_loc_free(loch);
	return (B_TRUE);
}

static boolean_t
valid_enm(const char *name)
{
	nwam_enm_handle_t enmh;

	if (nwam_enm_read(name, 0, &enmh) != NWAM_SUCCESS)
		return (B_FALSE);
	nwam_enm_free(enmh);
	return (B_TRUE);
}

static boolean_t
valid_ncp(const char *name)
{
	nwam_ncp_handle_t ncph;

	if (nwam_ncp_read(name, 0, &ncph) != NWAM_SUCCESS)
		return (B_FALSE);
	nwam_ncp_free(ncph);
	return (B_TRUE);
}

static boolean_t
valid_ncu(const char *name)
{
	nwam_ncp_handle_t ncph;
	nwam_ncu_handle_t ncuh;
	nwam_error_t	ret;

	if ((ncph = determine_active_ncp()) == NULL)
		return (B_FALSE);

	ret = nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_ANY, 0, &ncuh);
	nwam_ncp_free(ncph);
	if (ret != NWAM_SUCCESS && ret != NWAM_ENTITY_MULTIPLE_VALUES)
		return (B_FALSE);
	nwam_ncu_free(ncuh);
	return (B_TRUE);
}

/*
 * Given a name, returns object type (loc, enm, ncp, or ncu) and how many
 * objects matched that name.
 */
static nwam_object_type_t
determine_object_type(const char *name, int *num)
{
	nwam_object_type_t type;
	int n = 0;

	/* see if a valid loc, enm, ncp and/or ncu exists with given name */
	if (valid_loc(name)) {
		n++;
		type = NWAM_OBJECT_TYPE_LOC;
	}
	if (valid_enm(name)) {
		n++;
		type = NWAM_OBJECT_TYPE_ENM;
	}
	if (valid_ncp(name)) {
		n++;
		type = NWAM_OBJECT_TYPE_NCP;
	}
	if (valid_ncu(name)) {
		n++;
		type = NWAM_OBJECT_TYPE_NCU;
	}

	/* if n > 1, then it means *type was set multiple times, undo it */
	if (n != 1)
		type = NWAM_OBJECT_TYPE_UNKNOWN;

	*num = n;
	return (type);
}

/*
 * Parses argv array and populates object_type and name.
 * Program exits on failure.
 */
static void
parse_argv(int argc, char *argv[], int cmd_num, nwam_object_type_t *object_type,
    nwam_ncu_type_t *ncu_type, nwam_ncu_class_t *ncu_class, const char **name)
{
	int			arg;
	nwam_object_type_t	type = NWAM_OBJECT_TYPE_UNKNOWN;
	uint64_t		ncu = NWAM_NCU_TYPE_ANY;
	uint64_t		class = NWAM_NCU_CLASS_ANY;

	/* check argv for option */
	optind = 0;
	while ((arg = getopt(argc, argv, "?p:c:x")) != EOF) {
		switch (arg) {
		case 'p':
			type = nwam_string_to_object_type(optarg);
			if (type == NWAM_OBJECT_TYPE_UNKNOWN)
				die("Invalid profile-type: %s", optarg);
			break;
		case 'c':
			if (nwam_value_string_get_uint64(NWAM_NCU_PROP_CLASS,
			    optarg, &class) != NWAM_SUCCESS) {
				die("Invalid ncu-class: %s", optarg);
			}
			ncu = nwam_ncu_class_to_type(class);
			if (ncu == NWAM_NCU_TYPE_ANY ||
			    ncu == NWAM_NCU_TYPE_UNKNOWN)
				die("Invalid ncu-class: %s", optarg);
			break;
		case 'x':
			/* -x is only for list */
			if (cmd_num != CMD_LIST)
				die("-x can only be used with 'list'");
			extended_list = B_TRUE;
			break;
		case '?':
		default:
			die_usage(cmd_num);
		}
	}

	if (ncu != NWAM_NCU_TYPE_ANY) {
		/* If -c is given, -p must be NCU. If unspecified, assume NCU */
		if (type != NWAM_OBJECT_TYPE_UNKNOWN &&
		    type != NWAM_OBJECT_TYPE_NCU)
			die("'-c <ncu-class>' can only be used for ncu");

		type = NWAM_OBJECT_TYPE_NCU;
	}

	/* name is mandatory for enable and disable, but not for list */
	if (optind == (argc-1))
		*name = argv[optind];
	else if (argc != optind)
		die("too many profile names given");
	else if (cmd_num != CMD_LIST)
		die("no profile name given");

	/*
	 * No need to determine type for list.
	 * If -p is not given for enable or disable, then determine type.
	 */
	if (cmd_num != CMD_LIST && type == NWAM_OBJECT_TYPE_UNKNOWN) {
		int num = 0;

		type = determine_object_type(*name, &num);
		if (num == 0) {
			die("no profile matched '%s'", *name);
		} else if (num > 1) {
			die("more than one profile matched '%s' - use "
			    "'-p <profile-type>' to specify a profile type.",
			    *name);
		}
	}

	*object_type = type;
	*ncu_type = ncu;
	*ncu_class = class;
}

/* Enables/Disables profiles depending on boolean */
static nwam_error_t
loc_action(const char *name, boolean_t enable, char **realnamep)
{
	nwam_loc_handle_t loch;
	nwam_error_t ret;

	if ((ret = nwam_loc_read(name, 0, &loch)) != NWAM_SUCCESS)
		return (ret);

	if (enable)
		ret = nwam_loc_enable(loch);
	else
		ret = nwam_loc_disable(loch);

	(void) nwam_loc_get_name(loch, realnamep);
	nwam_loc_free(loch);
	return (ret);
}

static nwam_error_t
enm_action(const char *name, boolean_t enable, char **realnamep)
{
	nwam_enm_handle_t enmh;
	nwam_error_t ret;

	if ((ret = nwam_enm_read(name, 0, &enmh)) != NWAM_SUCCESS)
		return (ret);

	if (enable)
		ret = nwam_enm_enable(enmh);
	else
		ret = nwam_enm_disable(enmh);

	(void) nwam_enm_get_name(enmh, realnamep);
	nwam_enm_free(enmh);
	return (ret);
}

static nwam_error_t
ncu_action(const char *name, nwam_ncp_handle_t ncph, nwam_ncu_type_t type,
    boolean_t enable, char **realnamep)
{
	nwam_ncu_handle_t ncuh;
	nwam_error_t ret;
	boolean_t retrieved_ncph = B_FALSE;

	if (ncph == NULL) {
		if ((ncph = determine_active_ncp()) == NULL)
			return (NWAM_ENTITY_NOT_FOUND);
		retrieved_ncph = B_TRUE;
	}

	ret = nwam_ncu_read(ncph, name, type, 0, &ncuh);
	switch (ret) {
	case NWAM_SUCCESS:
		if (enable)
			ret = nwam_ncu_enable(ncuh);
		else
			ret = nwam_ncu_disable(ncuh);
		(void) nwam_ncu_get_name(ncuh, realnamep);
		nwam_ncu_free(ncuh);
		break;
	case NWAM_ENTITY_MULTIPLE_VALUES:
		/* Call ncu_action() for link and interface types */
		ret = ncu_action(name, ncph, NWAM_NCU_TYPE_LINK, enable,
		    realnamep);
		if (ret != NWAM_SUCCESS)
			break;

		ret = ncu_action(name, ncph, NWAM_NCU_TYPE_INTERFACE, enable,
		    realnamep);
		break;
	}
	if (retrieved_ncph)
		nwam_ncp_free(ncph);

	return (ret);
}

/*
 * If more than one type of profile with the same name, return error.
 * In such situations, the -p option must be used.
 * If a location is enabled when a different one is already enabled, then
 * that location is disabled automatically by nwamd.
 */
static void
enable_func(int argc, char *argv[])
{
	nwam_error_t		ret;
	nwam_object_type_t	type = NWAM_OBJECT_TYPE_UNKNOWN;
	nwam_ncu_type_t		ncu_type = NWAM_NCU_TYPE_ANY;
	nwam_ncu_class_t	ncu_class = NWAM_NCU_CLASS_ANY;
	const char		*name;
	char			*realname = NULL;

	/* parse_argv() returns only on success */
	parse_argv(argc, argv, CMD_ENABLE, &type, &ncu_type, &ncu_class, &name);

	/*
	 * NCPs and Locations don't need to disable the currently active
	 * profile - nwamd automatically switches to the new active profile.
	 * and will disable it if necessary.
	 */

	/* activate given profile */
	switch (type) {
	case NWAM_OBJECT_TYPE_LOC:
		ret = loc_action(name, B_TRUE, &realname);
		break;
	case NWAM_OBJECT_TYPE_ENM:
		ret = enm_action(name, B_TRUE, &realname);
		break;
	case NWAM_OBJECT_TYPE_NCP:
	{
		nwam_ncp_handle_t ncph;

		if ((ret = nwam_ncp_read(name, 0, &ncph)) != NWAM_SUCCESS)
			break;

		ret = nwam_ncp_enable(ncph);
		(void) nwam_ncp_get_name(ncph, &realname);
		nwam_ncp_free(ncph);
		break;
	}
	case NWAM_OBJECT_TYPE_NCU:
		ret = ncu_action(name, NULL, ncu_type, B_TRUE, &realname);
		break;
	}

	switch (ret) {
	case NWAM_SUCCESS:
		(void) printf(gettext("Enabling %s '%s'\n"),
		    nwam_object_type_to_string(type),
		    realname != NULL ? realname : name);
		break;
	case NWAM_ENTITY_NOT_MANUAL:
		die("Only profiles with manual activation-mode can be enabled");
		break;
	default:
		die_nwamerr(ret, "Could not enable %s '%s'",
		    nwam_object_type_to_string(type),
		    realname != NULL ? realname : name);
	}
	free(realname);
}

/*
 * Disables a given profile.  Similar to enable, the -p option must be used
 * if more than one type of profile is matched by the given name.
 */
static void
disable_func(int argc, char *argv[])
{
	nwam_error_t		ret;
	nwam_object_type_t	type = NWAM_OBJECT_TYPE_UNKNOWN;
	nwam_ncu_type_t		ncu_type = NWAM_NCU_TYPE_ANY;
	nwam_ncu_class_t	ncu_class = NWAM_NCU_CLASS_ANY;
	const char		*name;
	char			*realname = NULL;

	/* parse_argv() returns only on success */
	parse_argv(argc, argv, CMD_DISABLE, &type, &ncu_type, &ncu_class,
	    &name);

	/* deactivate the given profile */
	switch (type) {
	case NWAM_OBJECT_TYPE_LOC:
		ret = loc_action(name, B_FALSE, &realname);
		break;
	case NWAM_OBJECT_TYPE_ENM:
		ret = enm_action(name, B_FALSE, &realname);
		break;
	case NWAM_OBJECT_TYPE_NCU:
		ret = ncu_action(name, NULL, ncu_type, B_FALSE, &realname);
		break;
	case NWAM_OBJECT_TYPE_NCP:
		die("ncp's cannot be disabled.  Enable a different ncp to "
		    "switch to that ncp");
	}

	switch (ret) {
	case NWAM_SUCCESS:
		(void) printf(gettext("Disabling %s '%s'\n"),
		    nwam_object_type_to_string(type),
		    realname != NULL ? realname : name);
		break;
	case NWAM_ENTITY_NOT_MANUAL:
		die("Only profiles with manual activation-mode can be "
		    "disabled");
		break;
	default:
		die_nwamerr(ret, "Could not disable %s '%s'",
		    nwam_object_type_to_string(type),
		    realname != NULL ? realname : name);
	}
	free(realname);
}

/* prints each column */
static boolean_t
print_list_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
{
	profile_entry_t *pent = ofarg->ofmt_cbarg;

	switch (ofarg->ofmt_id) {
	case LIST_TYPE:
		/* ncu:ip or ncu:phys for NCUs; ncp, loc, enm for others */
		if (pent->p_type == NWAM_OBJECT_TYPE_NCU) {
			const char *class;
			if (nwam_uint64_get_value_string(NWAM_NCU_PROP_CLASS,
			    pent->p_ncu_class, &class) != NWAM_SUCCESS)
				class = ""; /* empty */
			(void) snprintf(buf, bufsize, "%s:%s",
			    nwam_object_type_to_string(pent->p_type), class);
		} else {
			(void) strlcpy(buf,
			    nwam_object_type_to_string(pent->p_type), bufsize);
		}
		break;
	case LIST_PROFILE:
		(void) strlcpy(buf, pent->p_name, bufsize);
		break;
	case LIST_STATE:
		(void) strlcpy(buf, nwam_state_to_string(pent->p_state),
		    bufsize);
		break;
	case LIST_AUXSTATE:
		(void) strlcpy(buf,
		    nwam_aux_state_to_string(pent->p_aux_state), bufsize);
		break;
	default:
		die("invalid print_list_cb() input: %d", ofarg->ofmt_id);
		break;
	}
	return (B_TRUE);
}

/* returns the state and auxilliary state of the object */
static nwam_state_t
determine_object_state(nwam_object_type_t type, void *handle,
    nwam_aux_state_t *aux_statep)
{
	nwam_state_t state;
	nwam_aux_state_t astate;
	nwam_error_t ret;

	switch (type) {
	case NWAM_OBJECT_TYPE_ENM:
		ret = nwam_enm_get_state(handle, &state, &astate);
		break;
	case NWAM_OBJECT_TYPE_LOC:
		ret = nwam_loc_get_state(handle, &state, &astate);
		break;
	case NWAM_OBJECT_TYPE_NCP:
		ret = nwam_ncp_get_state(handle, &state, &astate);
		break;
	case NWAM_OBJECT_TYPE_NCU:
		ret = nwam_ncu_get_state(handle, &state, &astate);
		break;
	default:
		/* NOTREACHED */
		break;
	}

	if (ret == NWAM_PERMISSION_DENIED) {
		die_nwamerr(ret, "could not get object state");
	} else if (ret != NWAM_SUCCESS) {
		state = NWAM_STATE_UNINITIALIZED;
		astate = NWAM_AUX_STATE_UNINITIALIZED;
	}

	if (aux_statep != NULL)
		*aux_statep = astate;
	return (state);
}

/* populate profile_entry_t with values for object with given handle */
static int
add_to_profile_entry(nwam_object_type_t type, void *handle,
    profile_entry_t *pent)
{
	char		*name;
	nwam_error_t	ret;

	pent->p_type = type;
	if (type == NWAM_OBJECT_TYPE_NCU) {
		nwam_ncu_class_t class;
		if ((ret = nwam_ncu_get_ncu_class(handle, &class))
		    != NWAM_SUCCESS)
			return (ret);
		pent->p_ncu_class = class;
	} else {
		pent->p_ncu_class = -1;
	}

	switch (type) {
	case NWAM_OBJECT_TYPE_ENM:
		ret = nwam_enm_get_name(handle, &name);
		break;
	case NWAM_OBJECT_TYPE_LOC:
		ret = nwam_loc_get_name(handle, &name);
		break;
	case NWAM_OBJECT_TYPE_NCP:
		ret = nwam_ncp_get_name(handle, &name);
		break;
	case NWAM_OBJECT_TYPE_NCU:
		ret = nwam_ncu_get_name(handle, &name);
		break;
	default:
		/* NOTREACHED */
		break;
	}
	if (ret != NWAM_SUCCESS) {
		return (ret);
	}
	(void) strlcpy(pent->p_name, name, sizeof (pent->p_name));
	free(name);

	pent->p_state = determine_object_state(type, handle,
	    &pent->p_aux_state);

	return (NWAM_SUCCESS);
}

/* callback functions used by walk */

static int
list_ncu_cb(nwam_ncu_handle_t ncuh, void *arg)
{
	ofmt_handle_t	ofmt = arg;
	profile_entry_t pent;
	nwam_error_t	ret;

	bzero(&pent, sizeof (profile_entry_t));
	ret = add_to_profile_entry(NWAM_OBJECT_TYPE_NCU, ncuh, &pent);
	if (ret != NWAM_SUCCESS)
		die_nwamerr(ret, "could not add ncu to list");
	ofmt_print(ofmt, &pent);
	return (0);
}

static int
list_ncp_cb(nwam_ncp_handle_t ncph, void *arg)
{
	ofmt_handle_t	ofmt = arg;
	profile_entry_t pent;
	nwam_error_t	ret;
	nwam_state_t	state;

	bzero(&pent, sizeof (profile_entry_t));
	ret = add_to_profile_entry(NWAM_OBJECT_TYPE_NCP, ncph, &pent);
	if (ret != NWAM_SUCCESS)
		die_nwamerr(ret, "could not add ncp to list");
	ofmt_print(ofmt, &pent);

	state = determine_object_state(NWAM_OBJECT_TYPE_NCP, ncph, NULL);
	if (state == NWAM_STATE_ONLINE) {
		(void) nwam_ncp_walk_ncus(ncph, list_ncu_cb, ofmt,
		    NWAM_FLAG_NCU_TYPE_ALL, NULL);
	}
	return (0);
}

static int
list_loc_cb(nwam_loc_handle_t loch, void *arg)
{
	ofmt_handle_t	ofmt = arg;
	profile_entry_t pent;
	nwam_error_t	ret;

	bzero(&pent, sizeof (profile_entry_t));
	ret = add_to_profile_entry(NWAM_OBJECT_TYPE_LOC, loch, &pent);
	if (ret != NWAM_SUCCESS)
		die_nwamerr(ret, "could not add loc to list");
	ofmt_print(ofmt, &pent);
	return (0);
}

static int
list_enm_cb(nwam_enm_handle_t enmh, void *arg)
{
	ofmt_handle_t	ofmt = arg;
	profile_entry_t pent;
	nwam_error_t	ret;

	bzero(&pent, sizeof (profile_entry_t));
	ret = add_to_profile_entry(NWAM_OBJECT_TYPE_ENM, enmh, &pent);
	if (ret != NWAM_SUCCESS)
		die_nwamerr(ret, "could not add enm to list");
	ofmt_print(ofmt, &pent);
	return (0);
}

/*
 * lists all profiles and their state
 */
static void
list_func(int argc, char *argv[])
{
	nwam_error_t		ret = NWAM_SUCCESS;
	nwam_object_type_t	type = NWAM_OBJECT_TYPE_UNKNOWN;
	nwam_ncu_type_t		ncu_type = NWAM_NCU_TYPE_ANY;
	nwam_ncu_class_t	ncu_class = NWAM_NCU_CLASS_ANY;
	char			*name = NULL;

	ofmt_handle_t	ofmt;
	ofmt_status_t	oferr;
	char		*default_fields = "type,profile,state";
	char		*extended_fields = "type,profile,state,auxiliary state";
	char		*fields = NULL;

	/* parse_argv() returns only on success */
	parse_argv(argc, argv, CMD_LIST, &type, &ncu_type, &ncu_class,
	    (const char **)&name);

	if (extended_list)
		fields = extended_fields;
	else
		fields = default_fields;
	oferr = ofmt_open(fields, list_fields, 0, 0, &ofmt);
	if (oferr != OFMT_SUCCESS) {
		char buf[OFMT_BUFSIZE];
		(void) ofmt_strerror(ofmt, oferr, buf, sizeof (buf));
		die("ofmt_open() failed: %s", buf);
	}

	/* object-name given in command-line */
	if (name != NULL) {
		boolean_t found = B_FALSE;

		/*
		 * If objects with different types have the same name
		 * (type = UNKNOWN), then try to open handle for each object
		 * and print if successful.
		 */
		if (type == NWAM_OBJECT_TYPE_NCP ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			nwam_ncp_handle_t ncph;
			if (nwam_ncp_read(name, 0, &ncph) == NWAM_SUCCESS) {
				found = B_TRUE;
				(void) list_ncp_cb(ncph, ofmt);
				nwam_ncp_free(ncph);
			}
		}
		if (type == NWAM_OBJECT_TYPE_NCU ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			nwam_ncp_handle_t ncph;
			nwam_ncu_handle_t ncuh;

			if ((ncph = determine_active_ncp()) != NULL) {
				ret = nwam_ncu_read(ncph, name, ncu_type, 0,
				    &ncuh);
				if (ret == NWAM_ENTITY_MULTIPLE_VALUES) {
					found = B_TRUE;
					if (nwam_ncu_read(ncph, name,
					    NWAM_NCU_TYPE_LINK, 0, &ncuh)
					    == NWAM_SUCCESS) {
						(void) list_ncu_cb(ncuh, ofmt);
						nwam_ncu_free(ncuh);
					}
					if (nwam_ncu_read(ncph, name,
					    NWAM_NCU_TYPE_INTERFACE, 0, &ncuh)
					    == NWAM_SUCCESS) {
						(void) list_ncu_cb(ncuh, ofmt);
						nwam_ncu_free(ncuh);
					}
				} else if (ret == NWAM_SUCCESS) {
					found = B_TRUE;
					(void) list_ncu_cb(ncuh, ofmt);
					nwam_ncu_free(ncuh);
				}
				nwam_ncp_free(ncph);
			}
		}
		if (type == NWAM_OBJECT_TYPE_LOC ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			nwam_loc_handle_t loch;
			if (nwam_loc_read(name, 0, &loch) == NWAM_SUCCESS) {
				found = B_TRUE;
				(void) list_loc_cb(loch, ofmt);
				nwam_loc_free(loch);
			}
		}
		if (type == NWAM_OBJECT_TYPE_ENM ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			nwam_enm_handle_t enmh;
			if (nwam_enm_read(name, 0, &enmh) == NWAM_SUCCESS) {
				found = B_TRUE;
				(void) list_enm_cb(enmh, ofmt);
				nwam_enm_free(enmh);
			}
		}
		/* If at least object is found, don't return error */
		if (found)
			ret = NWAM_SUCCESS;
		else
			ret = NWAM_ENTITY_NOT_FOUND;
	}

	/* object-name not given in command-line */
	if (name == NULL) {
		/*
		 * If type given (type != UNKNOWN), just walk objects in that
		 * type.  Otherwise, walk all ncp, ncu, loc and enm.
		 */
		if (type == NWAM_OBJECT_TYPE_NCP ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			ret = nwam_walk_ncps(list_ncp_cb, ofmt, 0, NULL);
			if (ret != NWAM_SUCCESS)
				goto done;
		}
		/* no UNKNOWN for NCUs.  They walked with active NCP above */
		if (type == NWAM_OBJECT_TYPE_NCU) {
			nwam_ncp_handle_t ncph;
			if ((ncph = determine_active_ncp()) != NULL) {
				ret = nwam_ncp_walk_ncus(ncph, list_ncu_cb,
				    ofmt, nwam_ncu_class_to_flag(ncu_class),
				    NULL);
				nwam_ncp_free(ncph);
				if (ret != NWAM_SUCCESS)
					goto done;
			}
		}
		if (type == NWAM_OBJECT_TYPE_LOC ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			ret = nwam_walk_locs(list_loc_cb, ofmt,
			    NWAM_FLAG_ACTIVATION_MODE_ALL, NULL);
			if (ret != NWAM_SUCCESS)
				goto done;
		}
		if (type == NWAM_OBJECT_TYPE_ENM ||
		    type == NWAM_OBJECT_TYPE_UNKNOWN) {
			ret = nwam_walk_enms(list_enm_cb, ofmt,
			    NWAM_FLAG_ACTIVATION_MODE_ALL, NULL);
			if (ret != NWAM_SUCCESS)
				goto done;
		}
	}

done:
	ofmt_close(ofmt);
	if (ret == NWAM_ENTITY_NOT_FOUND && name != NULL)
		die("no profile matched '%s'", name);
	else if (ret != NWAM_SUCCESS)
		die_nwamerr(ret, "list failed during walk");
}

/*
 * Print NWAM events.
 */
static void
eventhandler(nwam_event_t event)
{
	char description[DESCRIPTION_WIDTH];
	char statestr[DESCRIPTION_WIDTH];
	char objstr[DESCRIPTION_WIDTH];
	char *object = NULL;
	const char *action = NULL;
	char *state = NULL;
	boolean_t display = B_TRUE;
	int i;
	nwam_wlan_t *wlans;

	(void) strlcpy(description, "-", sizeof (description));

	switch (event->nwe_type) {
	case NWAM_EVENT_TYPE_OBJECT_ACTION:
		action = nwam_action_to_string
		    (event->nwe_data.nwe_object_action.nwe_action);
		(void) snprintf(objstr, sizeof (objstr), "%s %s",
		    nwam_object_type_to_string
		    (event->nwe_data.nwe_object_action.nwe_object_type),
		    event->nwe_data.nwe_object_action.nwe_name);
		object = objstr;
		break;

	case NWAM_EVENT_TYPE_OBJECT_STATE:
		(void) snprintf(statestr, sizeof (statestr), "%s, %s",
		    nwam_state_to_string
		    (event->nwe_data.nwe_object_state.nwe_state),
		    nwam_aux_state_to_string
		    (event->nwe_data.nwe_object_state.nwe_aux_state));
		state = statestr;

		(void) snprintf(objstr, sizeof (objstr), "%s %s",
		    nwam_object_type_to_string
		    (event->nwe_data.nwe_object_state.nwe_object_type),
		    event->nwe_data.nwe_object_state.nwe_name);
		object = objstr;
		break;

	case NWAM_EVENT_TYPE_PRIORITY_GROUP:
		(void) snprintf(description, DESCRIPTION_WIDTH,
		    "priority-group: %d",
		    event->nwe_data.nwe_priority_group_info.nwe_priority);
		break;

	case NWAM_EVENT_TYPE_WLAN_SCAN_REPORT:
		(void) printf("%-*s \n", EVENT_WIDTH,
		    nwam_event_type_to_string(event->nwe_type));
		wlans = event->nwe_data.nwe_wlan_info.nwe_wlans;
		for (i = 0;
		    i < event->nwe_data.nwe_wlan_info.nwe_num_wlans;
		    i++) {
			(void) snprintf(description, DESCRIPTION_WIDTH,
			    "%d: %c%c ESSID %s BSSID %s", i + 1,
			    wlans[i].nww_selected ? 'S' : '-',
			    wlans[i].nww_connected ? 'C' : '-',
			    wlans[i].nww_essid, wlans[i].nww_bssid);
			(void) printf("%-*s %-*s\n", EVENT_WIDTH, "-",
			    DESCRIPTION_WIDTH, description);
		}
		display = B_FALSE;
		break;

	case NWAM_EVENT_TYPE_WLAN_NEED_CHOICE:
		(void) printf("%-*s \n", EVENT_WIDTH,
		    nwam_event_type_to_string(event->nwe_type));
		display = B_FALSE;
		break;

	case NWAM_EVENT_TYPE_WLAN_NEED_KEY:
		(void) printf("%-*s \n", EVENT_WIDTH,
		    nwam_event_type_to_string(event->nwe_type));
		display = B_FALSE;
		break;

	case NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT:
		(void) snprintf(description, DESCRIPTION_WIDTH,
		    gettext("connect to WLAN ESSID %s, BSSID %s %s"),
		    event->nwe_data.nwe_wlan_info.nwe_wlans[0].nww_essid,
		    event->nwe_data.nwe_wlan_info.nwe_wlans[0].nww_bssid,
		    event->nwe_data.nwe_wlan_info.nwe_connected ?
		    "succeeded" : "failed");
		break;

	case NWAM_EVENT_TYPE_INFO:
		(void) snprintf(description, sizeof (description),
		    "%s", event->nwe_data.nwe_info.nwe_message);
		break;

	case NWAM_EVENT_TYPE_IF_ACTION:
		action = nwam_action_to_string
		    (event->nwe_data.nwe_if_action.nwe_action);
		object = event->nwe_data.nwe_if_action.nwe_name;
		break;

	case NWAM_EVENT_TYPE_IF_STATE:
		object = event->nwe_data.nwe_if_state.nwe_name;
		if (event->nwe_data.nwe_if_state.nwe_addr_valid) {
			struct sockaddr_storage *address =
			    &(event->nwe_data.nwe_if_state.nwe_addr);
			struct sockaddr_in *v4addr;
			struct sockaddr_in6 *v6addr;
			char addrstr[NWAM_MAX_VALUE_LEN];

			switch (address->ss_family) {
			case AF_INET:
				v4addr = (struct sockaddr_in *)address;
				(void) inet_ntop(AF_INET, &v4addr->sin_addr,
				    addrstr, sizeof (addrstr));
				break;
			case AF_INET6:
				v6addr = (struct sockaddr_in6 *)address;
				(void) inet_ntop(AF_INET6, &v6addr->sin6_addr,
				    addrstr, sizeof (addrstr));
				break;
			}
			(void) snprintf(statestr, sizeof (statestr),
			    "index %d flags 0x%x address %s",
			    event->nwe_data.nwe_if_state.nwe_index,
			    event->nwe_data.nwe_if_state.nwe_flags, addrstr);
		} else {
			(void) snprintf(statestr, sizeof (statestr),
			    "(%d) flags %x",
			    event->nwe_data.nwe_if_state.nwe_index,
			    event->nwe_data.nwe_if_state.nwe_flags);
		}
		state = statestr;
		break;

	case NWAM_EVENT_TYPE_LINK_ACTION:
		action = nwam_action_to_string
		    (event->nwe_data.nwe_link_action.nwe_action);
		object = event->nwe_data.nwe_link_action.nwe_name;
		break;

	case NWAM_EVENT_TYPE_LINK_STATE:
		state = event->nwe_data.nwe_link_state.nwe_link_up ?
		    "up" : "down";
		object = event->nwe_data.nwe_link_state.nwe_name;
		break;
	}

	if (object != NULL && action != NULL) {
		(void) snprintf(description, sizeof (description),
		    "%s -> action %s", object, action);
	} else if (object != NULL && state != NULL) {
		(void) snprintf(description, sizeof (description),
		    "%s -> state %s", object, state);
	}

	if (display) {
		(void) printf("%-*s %-*s\n", EVENT_WIDTH,
		    nwam_event_type_to_string(event->nwe_type),
		    DESCRIPTION_WIDTH,
		    description);
	}
}

/*
 * listens for events and displays them via the eventhandler() function above.
 */
/* ARGSUSED */
static void
show_events_func(int argc, char *argv[])
{
	nwam_error_t err;
	nwam_event_t event;

	err = nwam_events_init();

	if (err != NWAM_SUCCESS)
		die_nwamerr(err, "could not bind to receive events");

	/* print header */
	(void) printf("%-*s %-*s\n", EVENT_WIDTH, "EVENT",
	    DESCRIPTION_WIDTH, "DESCRIPTION");

	do {
		/*
		 * Needed for stdout redirection to ensure event output is
		 * regularly flushed to file.
		 */
		(void) fflush(stdout);
		err = nwam_event_wait(&event);
		if (err == NWAM_SUCCESS) {
			eventhandler(event);
			nwam_event_free(event);
		}
	} while (err == NWAM_SUCCESS);
	die_nwamerr(err, "event handling stopped");
}

/* May need to convert case-insensitive link name match to case-sensitive one */
static nwam_error_t
name_to_linkname(char *name, char **linknamep)
{
	nwam_error_t err;
	nwam_ncp_handle_t ncph = NULL;
	nwam_ncu_handle_t ncuh = NULL;

	if ((ncph = determine_active_ncp()) == NULL)
		return (NWAM_ENTITY_NOT_FOUND);

	err = nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK, 0, &ncuh);
	if (err == NWAM_SUCCESS)
		err = nwam_ncu_get_name(ncuh, linknamep);

	nwam_ncp_free(ncph);
	nwam_ncu_free(ncuh);
	return (err);
}

static void
scan_wifi_func(int argc, char *argv[])
{
	nwam_error_t err;
	char *linkname = NULL;

	if (argc != 1)
		die_usage(CMD_SCAN_WIFI);

	if ((err = name_to_linkname(argv[0], &linkname)) != NWAM_SUCCESS)
		die_nwamerr(err, "scan request failed for %s", argv[0]);

	err = nwam_wlan_scan(linkname);

	if (err != NWAM_SUCCESS)
		die_nwamerr(err, "scan request failed for %s", linkname);

	free(linkname);
}

static void
select_wifi_func(int argc, char *argv[])
{
	nwam_error_t err;
	char *linkname = NULL;
	uint_t i, choice, num_wlans = 0;
	uint32_t security_mode;
	boolean_t have_key = B_FALSE;
	nwam_wlan_t *wlans = NULL;
	char choicestr[NWAM_MAX_VALUE_LEN];
	char modestr[NWAM_MAX_VALUE_LEN];
	char essid[NWAM_MAX_VALUE_LEN];
	char bssid[NWAM_MAX_VALUE_LEN];

	if (argc != 1)
		die_usage(CMD_SELECT_WIFI);

	if ((err = name_to_linkname(argv[0], &linkname)) != NWAM_SUCCESS) {
		die_nwamerr(err, "could not retrieve scan results for %s",
		    argv[0]);
	}
	err = nwam_wlan_get_scan_results(linkname, &num_wlans, &wlans);

	if (err != NWAM_SUCCESS) {
		die_nwamerr(err, "could not retrieve scan results for %s",
		    linkname);
	}
	bssid[0] = '\0';

	/* Loop until valid selection made */
	for (;;) {
		(void) printf("\n");
		/* Display WLAN choices for user to select from */
		for (i = 0; i < num_wlans; i++) {
			(void) printf("%d: ESSID %s BSSID %s\n",
			    i + 1, wlans[i].nww_essid, wlans[i].nww_bssid);
		}
		(void) printf(gettext("%d: Other\n"), i + 1);

		(void) printf(gettext("\nChoose WLAN to connect to [1-%d]: "),
		    i + 1);

		if (fgets(choicestr, sizeof (choicestr), stdin) != NULL &&
		    (choice = atoi(choicestr)) >= 1 && choice <= (i + 1))
			break;
	}

	if (choice == i + 1 || wlans[choice - 1].nww_essid[0] == '\0') {
		nwam_known_wlan_handle_t kwh = NULL;
		nwam_value_t keynameval = NULL;

		/* If "Other" or a hidden WLAN is selected, ask for ESSID */
		do {
			(void) printf(gettext("\nEnter WLAN name: "));
			while (fgets(essid, sizeof (essid), stdin) == NULL) {}
			essid[strlen(essid) - 1] = '\0';
		} while (strspn(essid, " \t") == strlen(essid));

		/* If "Other" was selected, secmode must be specified. */
		if (choice == i + 1) {
			for (;;) {
				(void) printf(gettext("1: None\n"));
				(void) printf(gettext("2: WEP\n"));
				(void) printf(gettext("3: WPA\n"));
				(void) printf(gettext("Enter security mode: "));
				if (fgets(modestr, sizeof (choicestr), stdin)
				    != NULL &&
				    (security_mode = atoi(modestr)) >= 1 &&
				    security_mode <= 3)
					break;
			}
		} else {
			security_mode = wlans[choice - 1].nww_security_mode;
			have_key = wlans[choice - 1].nww_have_key;
		}

		/*
		 * We have to determine if we have a key for this ESSID from
		 * the known WLAN list, since we cannot determine this from
		 * the scan results.
		 */
		if (nwam_known_wlan_read(essid, 0, &kwh) == NWAM_SUCCESS &&
		    nwam_known_wlan_get_prop_value(kwh,
		    NWAM_KNOWN_WLAN_PROP_KEYNAME, &keynameval) == NWAM_SUCCESS)
			have_key = B_TRUE;
		else
			have_key = B_FALSE;

		nwam_value_free(keynameval);
		nwam_known_wlan_free(kwh);
	} else {
		(void) strlcpy(essid, wlans[choice - 1].nww_essid,
		    sizeof (essid));
		(void) strlcpy(bssid, wlans[choice - 1].nww_bssid,
		    sizeof (bssid));
		security_mode = wlans[choice - 1].nww_security_mode;
		have_key = wlans[choice - 1].nww_have_key;
	}

	if (security_mode != DLADM_WLAN_SECMODE_NONE && !have_key) {
		uint_t keyslot = 1;
		char key[NWAM_MAX_VALUE_LEN];
		char slotstr[NWAM_MAX_VALUE_LEN];

		do {
			(void) printf(gettext("\nEnter WLAN key for "
			    "ESSID %s: "), essid);
			while (fgets(key, sizeof (key), stdin) == NULL) {}
			key[strlen(key) - 1] = '\0';
		} while (strspn(key, " \t") == strlen(key));

		if (security_mode == DLADM_WLAN_SECMODE_WEP) {
			for (;;) {
				(void) printf(
				    gettext("\nEnter key slot [1-4]: "));
				if (fgets(slotstr, sizeof (slotstr), stdin)
				    != NULL && (keyslot = atoi(slotstr)) >= 1 &&
				    keyslot <= 4)
					break;
			}
		}

		err = nwam_wlan_set_key(linkname, essid, NULL, security_mode,
		    keyslot, key);
		if (err != NWAM_SUCCESS)
			die_nwamerr(err, "could not set WiFi key");
	}
	err = nwam_wlan_select(linkname, essid, bssid[0] != '\0' ? bssid : NULL,
	    security_mode, B_TRUE);
	if (err != NWAM_SUCCESS)
		die_nwamerr(err, "could not select WLAN %s", essid);
	free(wlans);
	free(linkname);
}

int
main(int argc, char *argv[])
{
	int i;
	char *state;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	if ((execname = strrchr(argv[0], '/')) == NULL)
		execname = argv[0];
	else
		execname++;

	if (argc < 2) {
		usage(B_FALSE);
		exit(EXIT_FAILURE);
	}

	for (i = CMD_MIN; i <= CMD_MAX; i++) {
		if (strcmp(argv[1], cmd_to_str(i)) == 0) {
			if (cmdtab[i].cmd_needs_nwamd) {
				state = smf_get_state(NWAM_FMRI);
				if (state == NULL || strcmp(state,
				    SCF_STATE_STRING_ONLINE) != 0) {
					free(state);
					die("enable '%s' to use '%s %s'",
					    NWAM_FMRI, execname,
					    cmd_to_str(cmdtab[i].cmd_num));
				}
				free(state);
			}

			cmdtab[i].cmd_handler(argc - 2, &(argv[2]));

			exit(EXIT_SUCCESS);
		}
	}

	(void) fprintf(stderr, gettext("%s: unknown subcommand '%s'\n"),
	    execname, argv[1]);
	usage(B_FALSE);

	return (1);
}