root/usr/src/cmd/cmd-inet/usr.sbin/nwamadm/nwamadm.c
/*
 * 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 <libinetutil.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             15      /* width of EVENT column */
#define DESCRIPTION_WIDTH       64      /* 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_storage *netmask =
                            &(event->nwe_data.nwe_if_state.nwe_netmask);
                        struct sockaddr_in *v4addr;
                        struct sockaddr_in6 *v6addr;
                        char addrstr[NWAM_MAX_VALUE_LEN];
                        int plen = mask2plen((struct sockaddr *)netmask);

                        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),
                            "flags %x addr %s/%d",
                            event->nwe_data.nwe_if_state.nwe_flags,
                            addrstr, plen);
                } else {
                        (void) snprintf(statestr, sizeof (statestr),
                            "flags %x", 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, bssid, 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);
}