root/usr/src/cmd/cmd-inet/usr.sbin/ipsecutils/ikeadm.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) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/sysconf.h>
#include <string.h>
#include <strings.h>
#include <libintl.h>
#include <locale.h>
#include <ctype.h>
#include <time.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
#include <assert.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <door.h>
#include <setjmp.h>

#include <ipsec_util.h>
#include <ikedoor.h>

static int      doorfd = -1;

/*
 * These are additional return values for the command line parsing
 * function (parsecmd()).  They are specific to this utility, but
 * need to share the same space as the IKE_SVC_* defs, without conflicts.
 * So they're defined relative to the end of that range.
 */
#define IKEADM_HELP_GENERAL     IKE_SVC_MAX + 1
#define IKEADM_HELP_GET         IKE_SVC_MAX + 2
#define IKEADM_HELP_SET         IKE_SVC_MAX + 3
#define IKEADM_HELP_ADD         IKE_SVC_MAX + 4
#define IKEADM_HELP_DEL         IKE_SVC_MAX + 5
#define IKEADM_HELP_DUMP        IKE_SVC_MAX + 6
#define IKEADM_HELP_FLUSH       IKE_SVC_MAX + 7
#define IKEADM_HELP_READ        IKE_SVC_MAX + 8
#define IKEADM_HELP_WRITE       IKE_SVC_MAX + 9
#define IKEADM_HELP_TOKEN       IKE_SVC_MAX + 10
#define IKEADM_HELP_HELP        IKE_SVC_MAX + 11
#define IKEADM_EXIT             IKE_SVC_MAX + 12

/*
 * Disable default TAB completion for now (until some brave soul tackles it).
 */
/* ARGSUSED */
static
CPL_MATCH_FN(no_match)
{
        return (0);
}

static void command_complete(int s) __NORETURN;
static void usage(void) __NORETURN;

static void
command_complete(int s)
{
        if (interactive) {
                longjmp(env, 1);
        } else {
                exit(s);
        }
}

static void
usage(void)
{
        if (!interactive) {
                (void) fprintf(stderr, gettext("Usage:\t"
                    "ikeadm [ -hnp ] cmd obj [cmd-specific options]\n"));
                (void) fprintf(stderr, gettext("      \tikeadm help\n"));
        } else {
                (void) fprintf(stderr,
                    gettext("\nType help for usage info\n"));
        }

        command_complete(1);
}

static void
print_help()
{
        (void) printf(gettext("Valid commands and objects:\n"));
        (void) printf(
            "\tget   debug|priv|stats|p1|rule|preshared|defaults [%s]\n",
            gettext("identifier"));
        (void) printf("\tset   priv %s\n", gettext("level"));
        (void) printf("\tset   debug %s [%s]\n",
            gettext("level"), gettext("filename"));
        (void) printf("\tadd   rule|preshared {%s}|%s\n",
            gettext("definition"), gettext("filename"));
        (void) printf("\tdel   p1|rule|preshared %s\n", gettext("identifier"));
        (void) printf("\tdump  p1|rule|preshared|certcache|groups|"
            "encralgs|authalgs\n");
        (void) printf("\tflush p1|certcache\n");
        (void) printf("\tread  rule|preshared [%s]\n", gettext("filename"));
        (void) printf("\twrite rule|preshared %s\n", gettext("filename"));
        (void) printf("\ttoken <login|logout> %s\n",
            gettext("<PKCS#11 Token Object>"));
        (void) printf(
            "\thelp  [get|set|add|del|dump|flush|read|write|token|help]\n");
        (void) printf("\texit  %s\n", gettext("exit the program"));
        (void) printf("\tquit  %s\n", gettext("exit the program"));

        command_complete(0);
}

static void
print_get_help()
{
        (void) printf(
            gettext("This command gets information from in.iked.\n\n"));
        (void) printf(gettext("Objects that may be retrieved include:\n"));
        (void) printf("\tdebug\t\t");
        (void) printf(gettext("the current debug level\n"));
        (void) printf("\tpriv\t\t");
        (void) printf(gettext("the current privilege level\n"));
        (void) printf("\tstats\t\t");
        (void) printf(gettext("current usage statistics\n"));
        (void) printf("\tp1\t\t");
        (void) printf(gettext("a phase 1 SA, identified by\n"));
        (void) printf(gettext("\t\t\t  local_ip remote_ip OR\n"));
        (void) printf(gettext("\t\t\t  init_cookie resp_cookie\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("a phase 1 rule, identified by its label\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("a preshared key, identified by\n"));
        (void) printf(gettext("\t\t\t  local_ip remote_ip OR\n"));
        (void) printf(gettext("\t\t\t  local_id remote_id\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_set_help()
{
        (void) printf(gettext("This command sets values in in.iked.\n\n"));
        (void) printf(gettext("Objects that may be set include:\n"));
        (void) printf("\tdebug\t\t");
        (void) printf(gettext("change the debug level\n"));
        (void) printf("\tpriv\t\t");
        (void) printf(
            gettext("change the privilege level (may only be lowered)\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_add_help()
{
        (void) printf(
            gettext("This command adds items to in.iked's tables.\n\n"));
        (void) printf(gettext("Objects that may be set include:\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("a phase 1 policy rule\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("a preshared key\n"));
        (void) printf(
            gettext("\nObjects may be entered on the command-line, as a\n"));
        (void) printf(
            gettext("series of keywords and tokens contained in curly\n"));
        (void) printf(
            gettext("braces ('{', '}'); or the name of a file containing\n"));
        (void) printf(gettext("the object definition may be provided.\n\n"));
        (void) printf(
            gettext("For security purposes, preshared keys may only be\n"));
        (void) printf(
            gettext("entered on the command-line if ikeadm is running in\n"));
        (void) printf(gettext("interactive mode.\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_del_help()
{
        (void) printf(
            gettext("This command deletes an item from in.iked's tables.\n\n"));
        (void) printf(gettext("Objects that may be deleted include:\n"));
        (void) printf("\tp1\t\t");
        (void) printf(gettext("a phase 1 SA, identified by\n"));
        (void) printf(gettext("\t\t\t  local_ip remote_ip OR\n"));
        (void) printf(gettext("\t\t\t  init_cookie resp_cookie\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("a phase 1 rule, identified by its label\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("a preshared key, identified by\n"));
        (void) printf(gettext("\t\t\t  local_ip remote_ip OR\n"));
        (void) printf(gettext("\t\t\t  local_id remote_id\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_dump_help()
{
        (void) printf(
            gettext("This command dumps one of in.iked's tables.\n\n"));
        (void) printf(gettext("Tables that may be dumped include:\n"));
        (void) printf("\tp1\t\t");
        (void) printf(gettext("all phase 1 SAs\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("all phase 1 rules\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("all preshared keys\n"));
        (void) printf("\tcertcache\t");
        (void) printf(gettext("all cached certificates\n"));
        (void) printf("\tgroups\t\t");
        (void) printf(gettext("all implemented Diffie-Hellman groups\n"));
        (void) printf("\tencralgs\t");
        (void) printf(gettext("all encryption algorithms for IKE\n"));
        (void) printf("\tauthalgs\t");
        (void) printf(gettext("all authentication algorithms IKE\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_flush_help()
{
        (void) printf(
            gettext("This command clears one of in.iked's tables.\n\n"));
        (void) printf(gettext("Tables that may be flushed include:\n"));
        (void) printf("\tp1\t\t");
        (void) printf(gettext("all phase 1 SAs\n"));
        (void) printf("\tcertcache\t");
        (void) printf(gettext("all cached certificates\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_read_help()
{
        (void) printf(
            gettext("This command reads a new configuration file into\n"));
        (void) printf(
            gettext("in.iked, discarding the old configuration info.\n\n"));
        (void) printf(gettext("Sets of data that may be read include:\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("all phase 1 rules\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("all preshared keys\n\n"));
        (void) printf(
            gettext("A filename may be provided to specify a source file\n"));
        (void) printf(gettext("other than the default.\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_write_help()
{
        (void) printf(
            gettext("This command writes in.iked's current configuration\n"));
        (void) printf(gettext("out to a config file.\n\n"));
        (void) printf(gettext("Sets of data that may be written include:\n"));
        (void) printf("\trule\t\t");
        (void) printf(gettext("all phase 1 rules\n"));
        (void) printf("\tpreshared\t");
        (void) printf(gettext("all preshared keys\n\n"));
        (void) printf(
            gettext("A filename must be provided to specify the file to\n"));
        (void) printf(gettext("which the information should be written.\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_token_help()
{
        (void) printf(gettext(
            "This command logs IKE into and out of PKCS#11 tokens.\n\n"));
        (void) printf(gettext("Commands include:\n"));
        (void) printf("\tlogin <PKCS#11 Token Object>\t");
        (void) printf(gettext("log into token\n"));
        (void) printf("\tlogout <PKCS#11 Token Object>\t");
        (void) printf(gettext("log out of token\n\n"));
        (void) printf(
            gettext("The PKCS#11 Token Object name must be "
            "enclosed in quotation marks.\n"));
        (void) printf("\n");

        command_complete(0);
}

static void
print_help_help()
{
        (void) printf(
            gettext("This command provides information about commands.\n\n"));
        (void) printf(
            gettext("The 'help' command alone provides a list of valid\n"));
        (void) printf(
            gettext("commands, along with the valid objects for each.\n"));
        (void) printf(
            gettext("'help' followed by a valid command name provides\n"));
        (void) printf(gettext("further information about that command.\n"));
        (void) printf("\n");

        command_complete(0);
}

/*PRINTFLIKE1*/
static void
message(char *fmt, ...)
{
        va_list ap;
        char    msgbuf[BUFSIZ];

        va_start(ap, fmt);
        (void) vsnprintf(msgbuf, BUFSIZ, fmt, ap);
        (void) fprintf(stderr, gettext("ikeadm: %s\n"), msgbuf);
        va_end(ap);
}

static int
open_door(void)
{
        if (doorfd >= 0)
                (void) close(doorfd);
        doorfd = open(DOORNM, O_RDONLY);
        return (doorfd);
}

static ike_service_t *
ikedoor_call(char *reqp, int size, door_desc_t *descp, int ndesc)
{
        door_arg_t      arg;
        int retries = 0;

        arg.data_ptr = reqp;
        arg.data_size = size;
        arg.desc_ptr = descp;
        arg.desc_num = ndesc;
        arg.rbuf = (char *)NULL;
        arg.rsize = 0;

retry:
        if (door_call(doorfd, &arg) < 0) {
                if ((errno == EBADF) && ((++retries < 2) &&
                    (open_door() >= 0)))
                        goto retry;
                (void) fprintf(stderr,
                    gettext("Unable to communicate with in.iked\n"));
                Bail("door_call failed");
        }

        if ((ndesc > 0) && (descp->d_attributes & DOOR_RELEASE) &&
            ((errno == EBADF) || (errno == EFAULT))) {
                /* callers assume passed fds will be closed no matter what */
                (void) close(descp->d_data.d_desc.d_descriptor);
        }

        /* LINTED E_BAD_PTR_CAST_ALIGN */
        return ((ike_service_t *)arg.rbuf);
}

/*
 * Parsing functions
 */

/* stolen from ipseckey.c, with a second tier added */
static int
parsecmd(char *cmdstr, char *objstr)
{
#define MAXOBJS         11
        struct objtbl {
                char    *obj;
                int     token;
        };
        static struct cmdtbl {
                char            *cmd;
                int             null_obj_token;
                struct objtbl   objt[MAXOBJS];
        } table[] = {
                {"get", IKE_SVC_ERROR, {
                                {"debug",       IKE_SVC_GET_DBG},
                                {"priv",        IKE_SVC_GET_PRIV},
                                {"stats",       IKE_SVC_GET_STATS},
                                {"p1",          IKE_SVC_GET_P1},
                                {"rule",        IKE_SVC_GET_RULE},
                                {"preshared",   IKE_SVC_GET_PS},
                                {"defaults",    IKE_SVC_GET_DEFS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"set", IKE_SVC_ERROR, {
                                {"debug",       IKE_SVC_SET_DBG},
                                {"priv",        IKE_SVC_SET_PRIV},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"token", IKE_SVC_ERROR, {
                                {"login",       IKE_SVC_SET_PIN},
                                {"logout",      IKE_SVC_DEL_PIN},
                                {NULL,          IKE_SVC_ERROR},
                        }
                },
                {"add", IKE_SVC_ERROR, {
                                {"rule",        IKE_SVC_NEW_RULE},
                                {"preshared",   IKE_SVC_NEW_PS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"del", IKE_SVC_ERROR, {
                                {"p1",          IKE_SVC_DEL_P1},
                                {"rule",        IKE_SVC_DEL_RULE},
                                {"preshared",   IKE_SVC_DEL_PS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"dump", IKE_SVC_ERROR, {
                                {"p1",          IKE_SVC_DUMP_P1S},
                                {"rule",        IKE_SVC_DUMP_RULES},
                                {"preshared",   IKE_SVC_DUMP_PS},
                                {"certcache",   IKE_SVC_DUMP_CERTCACHE},
                                {"groups",      IKE_SVC_DUMP_GROUPS},
                                {"encralgs",    IKE_SVC_DUMP_ENCRALGS},
                                {"authalgs",    IKE_SVC_DUMP_AUTHALGS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"flush", IKE_SVC_ERROR, {
                                {"p1",          IKE_SVC_FLUSH_P1S},
                                {"certcache",   IKE_SVC_FLUSH_CERTCACHE},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"read", IKE_SVC_ERROR, {
                                {"rule",        IKE_SVC_READ_RULES},
                                {"preshared",   IKE_SVC_READ_PS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"write", IKE_SVC_ERROR, {
                                {"rule",        IKE_SVC_WRITE_RULES},
                                {"preshared",   IKE_SVC_WRITE_PS},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"help", IKEADM_HELP_GENERAL, {
                                {"get",         IKEADM_HELP_GET},
                                {"set",         IKEADM_HELP_SET},
                                {"add",         IKEADM_HELP_ADD},
                                {"del",         IKEADM_HELP_DEL},
                                {"dump",        IKEADM_HELP_DUMP},
                                {"flush",       IKEADM_HELP_FLUSH},
                                {"read",        IKEADM_HELP_READ},
                                {"write",       IKEADM_HELP_WRITE},
                                {"token",       IKEADM_HELP_TOKEN},
                                {"help",        IKEADM_HELP_HELP},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"exit", IKEADM_EXIT, {
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"quit", IKEADM_EXIT, {
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {"dbg", IKE_SVC_ERROR, {
                                {"rbdump",      IKE_SVC_DBG_RBDUMP},
                                {NULL,          IKE_SVC_ERROR}
                        }
                },
                {NULL,  IKE_SVC_ERROR, {
                                {NULL,          IKE_SVC_ERROR}
                        }
                }
        };
        struct cmdtbl   *ct = table;
        struct objtbl   *ot;

        if (cmdstr == NULL) {
                return (IKE_SVC_ERROR);
        }

        while (ct->cmd != NULL && strcmp(ct->cmd, cmdstr) != 0)
                ct++;
        ot = ct->objt;

        if (ct->cmd == NULL) {
                message(gettext("Unrecognized command '%s'"), cmdstr);
                return (ot->token);
        }

        if (objstr == NULL) {
                return (ct->null_obj_token);
        }

        while (ot->obj != NULL && strcmp(ot->obj, objstr) != 0)
                ot++;

        if (ot->obj == NULL)
                message(gettext("Unrecognized object '%s'"), objstr);

        return (ot->token);
}

/*
 * Parsing functions:
 * Parse command-line identification info.  All return -1 on failure,
 * or the number of cmd-line args "consumed" on success (though argc
 * and argv params are not actually modified).
 */

static int
parse_label(int argc, char **argv, char *label)
{
        if ((argc < 1) || (argv == NULL))
                return (-1);

        if (strlcpy(label, argv[0], MAX_LABEL_LEN) >= MAX_LABEL_LEN)
                return (-1);

        return (1);
}

/*
 * Parse a PKCS#11 token get the label.
 */
static int
parse_token(int argc, char **argv, char *token_label)
{
        if ((argc < 1) || (argv == NULL))
                return (-1);

        if (strlcpy(token_label, argv[0], PKCS11_TOKSIZE) >= PKCS11_TOKSIZE)
                return (-1);

        return (0);
}

/*
 * Parse an address off the command line. In the hpp param, either
 * return a hostent pointer (caller frees) or a pointer to a dummy_he_t
 * (must also be freed by the caller; both cases are handled by the
 * macro FREE_HE).  The new getipnodebyname() call does the Right Thing
 * (TM), even with raw addresses (colon-separated IPv6 or dotted decimal
 * IPv4).
 * (mostly stolen from ipseckey.c, though some tweaks were made
 * to better serve our purposes here.)
 */

typedef struct {
        struct hostent  he;
        char            *addtl[2];
} dummy_he_t;

static int
parse_addr(int argc, char **argv, struct hostent **hpp)
{
        int             hp_errno;
        struct hostent  *hp = NULL;
        dummy_he_t      *dhp;
        char            *addr1;

        if ((argc < 1) || (argv == NULL) || (argv[0] == NULL))
                return (-1);

        if (!nflag) {
                /*
                 * Try name->address first.  Assume AF_INET6, and
                 * get IPV4s, plus IPv6s iff IPv6 is configured.
                 */
                hp = getipnodebyname(argv[0], AF_INET6, AI_DEFAULT | AI_ALL,
                    &hp_errno);
        } else {
                /*
                 * Try a normal address conversion only.  malloc a
                 * dummy_he_t to construct a fake hostent.  Caller
                 * will know to free this one using free_he().
                 */
                dhp = (dummy_he_t *)malloc(sizeof (dummy_he_t));
                addr1 = (char *)malloc(sizeof (struct in6_addr));
                if (inet_pton(AF_INET6, argv[0], addr1) == 1) {
                        dhp->he.h_addr_list = dhp->addtl;
                        dhp->addtl[0] = addr1;
                        dhp->addtl[1] = NULL;
                        hp = &dhp->he;
                        dhp->he.h_addrtype = AF_INET6;
                        dhp->he.h_length = sizeof (struct in6_addr);
                } else if (inet_pton(AF_INET, argv[0], addr1) == 1) {
                        dhp->he.h_addr_list = dhp->addtl;
                        dhp->addtl[0] = addr1;
                        dhp->addtl[1] = NULL;
                        hp = &dhp->he;
                        dhp->he.h_addrtype = AF_INET;
                        dhp->he.h_length = sizeof (struct in_addr);
                } else {
                        hp = NULL;
                }
        }

        *hpp = hp;

        if (hp == NULL) {
                message(gettext("Unknown address %s."), argv[0]);
                return (-1);
        }

        return (1);
}

/*
 * Free a dummy_he_t structure that was malloc'd in parse_addr().
 * Unfortunately, callers of parse_addr don't want to know about
 * dummy_he_t structs, so all they have is a pointer to the struct
 * hostent; so that's what's passed in.  To manage this, we make
 * the assumption that the struct hostent is the first field in
 * the dummy_he_t, and therefore a pointer to it is a pointer to
 * the dummy_he_t.
 */
static void
free_he(struct hostent *hep)
{
        dummy_he_t      *p = (dummy_he_t *)hep;

        assert(p != NULL);

        if (p->addtl[0])
                free(p->addtl[0]);
        if (p->addtl[1])
                free(p->addtl[1]);

        free(p);
}

#define FREE_HE(x) \
        if (nflag) \
                free_he(x); \
        else \
                freehostent(x)

static void
headdr2sa(char *hea, struct sockaddr_storage *sa, int len)
{
        struct sockaddr_in      *sin;
        struct sockaddr_in6     *sin6;

        if (len == sizeof (struct in6_addr)) {
                /* LINTED E_BAD_PTR_CAST_ALIGN */
                if (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)hea)) {
                        sin = (struct sockaddr_in *)sa;
                        (void) memset(sin, 0, sizeof (*sin));
                        /* LINTED E_BAD_PTR_CAST_ALIGN */
                        IN6_V4MAPPED_TO_INADDR((struct in6_addr *)hea,
                            &sin->sin_addr);
                        sin->sin_family = AF_INET;
                } else {
                        sin6 = (struct sockaddr_in6 *)sa;
                        (void) memset(sin6, 0, sizeof (*sin6));
                        (void) memcpy(&sin6->sin6_addr, hea,
                            sizeof (struct in6_addr));
                        sin6->sin6_family = AF_INET6;
                }
        } else {
                sin = (struct sockaddr_in *)sa;
                (void) memset(sin, 0, sizeof (*sin));
                (void) memcpy(&sin->sin_addr, hea, sizeof (struct in_addr));
                sin->sin_family = AF_INET;
        }
}

/*
 * The possible ident-type keywords that might be used on the command
 * line.  This is a superset of the ones supported by ipseckey, those
 * in the ike config file, and those in ike.preshared.
 */
static keywdtab_t       idtypes[] = {
        /* ip, ipv4, and ipv6 are valid for preshared keys... */
        {SADB_IDENTTYPE_RESERVED,       "ip"},
        {SADB_IDENTTYPE_RESERVED,       "ipv4"},
        {SADB_IDENTTYPE_RESERVED,       "ipv6"},
        {SADB_IDENTTYPE_PREFIX,         "prefix"},
        {SADB_IDENTTYPE_PREFIX,         "ipv4-prefix"},
        {SADB_IDENTTYPE_PREFIX,         "ipv6-prefix"},
        {SADB_IDENTTYPE_PREFIX,         "subnet"},
        {SADB_IDENTTYPE_PREFIX,         "subnetv4"},
        {SADB_IDENTTYPE_PREFIX,         "subnetv6"},
        {SADB_IDENTTYPE_FQDN,           "fqdn"},
        {SADB_IDENTTYPE_FQDN,           "dns"},
        {SADB_IDENTTYPE_FQDN,           "domain"},
        {SADB_IDENTTYPE_FQDN,           "domainname"},
        {SADB_IDENTTYPE_USER_FQDN,      "user_fqdn"},
        {SADB_IDENTTYPE_USER_FQDN,      "mbox"},
        {SADB_IDENTTYPE_USER_FQDN,      "mailbox"},
        {SADB_X_IDENTTYPE_DN,           "dn"},
        {SADB_X_IDENTTYPE_DN,           "asn1dn"},
        {SADB_X_IDENTTYPE_GN,           "gn"},
        {SADB_X_IDENTTYPE_GN,           "asn1gn"},
        {SADB_X_IDENTTYPE_ADDR_RANGE,   "ipv4-range"},
        {SADB_X_IDENTTYPE_ADDR_RANGE,   "ipv6-range"},
        {SADB_X_IDENTTYPE_ADDR_RANGE,   "rangev4"},
        {SADB_X_IDENTTYPE_ADDR_RANGE,   "rangev6"},
        {SADB_X_IDENTTYPE_KEY_ID,       "keyid"},
        {0,                             NULL}
};

static int
parse_idtype(char *type, uint16_t *idnum)
{
        keywdtab_t      *idp;

        if (type == NULL)
                return (-1);

        for (idp = idtypes; idp->kw_str != NULL; idp++) {
                if (strcasecmp(idp->kw_str, type) == 0) {
                        if (idnum != NULL)
                                *idnum = idp->kw_tag;
                        return (1);
                }
        }

        return (-1);
}

/*
 * The sadb_ident_t is malloc'd, since its length varies;
 * so the caller must free() it when done with the data.
 */
static int
parse_ident(int argc, char **argv, sadb_ident_t **idpp)
{
        int             alloclen, consumed;
        sadb_ident_t    *idp;
        if ((argc < 2) || (argv == NULL) || (argv[0] == NULL) ||
            (argv[1] == NULL))
                return (-1);

        alloclen = sizeof (sadb_ident_t) + IKEDOORROUNDUP(strlen(argv[1]) + 1);
        *idpp = idp = (sadb_ident_t *)malloc(alloclen);
        if (idp == NULL)
                Bail("parsing identity");

        if ((consumed = parse_idtype(argv[0], &idp->sadb_ident_type)) < 0) {
                message(gettext("unknown identity type %s."), argv[0]);
                return (-1);
        }

        idp->sadb_ident_len = SADB_8TO64(alloclen);
        idp->sadb_ident_reserved = 0;
        idp->sadb_ident_id = 0;

        /* now copy in identity param */
        (void) strlcpy((char *)(idp + 1), argv[1],
            alloclen - (sizeof (sadb_ident_t)));

        return (++consumed);
}

static int
parse_cky(int argc, char **argv, uint64_t *ckyp)
{
        u_longlong_t    arg;

        if ((argc < 1) || (argv[0] == NULL))
                return (-1);

        errno = 0;
        arg = strtoull(argv[0], NULL, 0);
        if (errno != 0) {
                message(gettext("failed to parse cookie %s."), argv[0]);
                return (-1);
        }

        *ckyp = (uint64_t)arg;

        return (1);
}

static int
parse_addr_pr(int argc, char **argv, struct hostent **h1pp,
    struct hostent **h2pp)
{
        int     rtn, consumed = 0;

        if ((rtn = parse_addr(argc, argv, h1pp)) < 0) {
                return (-1);
        }
        consumed = rtn;
        argc -= rtn;
        argv += rtn;

        if ((rtn = parse_addr(argc, argv, h2pp)) < 0) {
                FREE_HE(*h1pp);
                return (-1);
        }
        consumed += rtn;

        return (consumed);
}

/*
 * The sadb_ident_ts are malloc'd, since their length varies;
 * so the caller must free() them when done with the data.
 */
static int
parse_ident_pr(int argc, char **argv, sadb_ident_t **id1pp,
    sadb_ident_t **id2pp)
{
        int     rtn, consumed = 0;

        if ((rtn = parse_ident(argc, argv, id1pp)) < 0) {
                return (-1);
        }
        consumed = rtn;
        argc -= rtn;
        argv += rtn;

        (*id1pp)->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;

        if ((rtn = parse_ident(argc, argv, id2pp)) < 0) {
                free(*id1pp);
                return (-1);
        }
        consumed += rtn;

        (*id2pp)->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;

        return (consumed);
}

static int
parse_cky_pr(int argc, char **argv, ike_cky_pr_t *cpr)
{
        int     rtn, consumed = 0;

        if ((rtn = parse_cky(argc, argv, &cpr->cky_i)) < 0) {
                return (-1);
        }
        consumed = rtn;
        argc -= rtn;
        argv += rtn;

        if ((rtn = parse_cky(argc, argv, &cpr->cky_r)) < 0) {
                return (-1);
        }
        consumed += rtn;

        return (consumed);
}

/*
 * Preshared key field types...used for parsing preshared keys that
 * have been entered on the command line.  The code to parse preshared
 * keys (parse_ps, parse_key, parse_psfldid, parse_ikmtype, ...) is
 * mostly duplicated from in.iked's readps.c.
 */
#define PSFLD_LOCID     1
#define PSFLD_LOCIDTYPE 2
#define PSFLD_REMID     3
#define PSFLD_REMIDTYPE 4
#define PSFLD_MODE      5
#define PSFLD_KEY       6

static keywdtab_t       psfldtypes[] = {
        {PSFLD_LOCID,           "localid"},
        {PSFLD_LOCIDTYPE,       "localidtype"},
        {PSFLD_REMID,           "remoteid"},
        {PSFLD_REMIDTYPE,       "remoteidtype"},
        {PSFLD_MODE,            "ike_mode"},
        {PSFLD_KEY,             "key"},
        {0,                     NULL}
};

static int
parse_psfldid(char *type, uint16_t *idnum)
{
        keywdtab_t      *pfp;

        if (type == NULL)
                return (-1);

        for (pfp = psfldtypes; pfp->kw_str != NULL; pfp++) {
                if (strcasecmp(pfp->kw_str, type) == 0) {
                        if (idnum != NULL)
                                *idnum = pfp->kw_tag;
                        return (1);
                }
        }

        return (-1);
}

static keywdtab_t       ikemodes[] = {
        {IKE_XCHG_IDENTITY_PROTECT,     "main"},
        {IKE_XCHG_AGGRESSIVE,           "aggressive"},
        {IKE_XCHG_IP_AND_AGGR,          "both"},
        {0,                             NULL}
};

static int
parse_ikmtype(char *mode, uint16_t *modenum)
{
        keywdtab_t      *ikmp;

        if (mode == NULL)
                return (-1);

        for (ikmp = ikemodes; ikmp->kw_str != NULL; ikmp++) {
                if (strcasecmp(ikmp->kw_str, mode) == 0) {
                        if (modenum != NULL)
                                *modenum = ikmp->kw_tag;
                        return (1);
                }
        }

        return (-1);
}

#define hd2num(hd) (((hd) >= '0' && (hd) <= '9') ? ((hd) - '0') : \
        (((hd) >= 'a' && (hd) <= 'f') ? ((hd) - 'a' + 10) : ((hd) - 'A' + 10)))

static uint8_t *
parse_key(char *input, uint_t *keybuflen, uint_t *lbits)
{
        uint8_t *keyp, *keybufp;
        uint_t  i, hexlen = 0, bits, alloclen;

        for (i = 0; input[i] != '\0' && input[i] != '/'; i++)
                hexlen++;

        if (input[i] == '\0') {
                bits = 0;
        } else {
                /* Have /nn. */
                input[i] = '\0';
                if (sscanf((input + i + 1), "%u", &bits) != 1)
                        return (NULL);

                /* hexlen is in nibbles */
                if (((bits + 3) >> 2) > hexlen)
                        return (NULL);

                /*
                 * Adjust hexlen down if user gave us too small of a bit
                 * count.
                 */
                if ((hexlen << 2) > bits + 3) {
                        hexlen = (bits + 3) >> 2;
                        input[hexlen] = '\0';
                }
        }

        /*
         * Allocate.  Remember, hexlen is in nibbles.
         */

        alloclen = (hexlen/2 + (hexlen & 0x1));
        keyp = malloc(alloclen);

        if (keyp == NULL)
                return (NULL);

        keybufp = keyp;
        *keybuflen = alloclen;
        if (bits == 0)
                *lbits = (hexlen + (hexlen & 0x1)) << 2;
        else
                *lbits = bits;

        /*
         * Read in nibbles.  Read in odd-numbered as shifted high.
         * (e.g. 123 becomes 0x1230).
         */
        for (i = 0; input[i] != '\0'; i += 2) {
                boolean_t second = (input[i + 1] != '\0');

                if (!isxdigit(input[i]) ||
                    (!isxdigit(input[i + 1]) && second)) {
                        free(keyp);
                        return (NULL);
                }
                *keyp = (hd2num(input[i]) << 4);
                if (second)
                        *keyp |= hd2num(input[i + 1]);
                else
                        break; /* out of for loop. */
                keyp++;
        }

        /* zero the remaining bits if we're a non-octet amount. */
        if (bits & 0x7)
                *((input[i] == '\0') ? keyp - 1 : keyp) &=
                    0xff << (8 - (bits & 0x7));
        return (keybufp);
}

/*
 * the ike_ps_t struct (plus trailing data) will be allocated here,
 * so it will need to be freed by the caller.
 */
static int
parse_ps(int argc, char **argv, ike_ps_t **presharedpp, int *len)
{
        uint_t          c = 0, locidlen, remidlen, keylen, keybits;
        uint_t          a_locidtotal = 0, a_remidtotal = 0;
        char            *locid, *remid, *locpfx = NULL, *rempfx = NULL;
        uint8_t         *keyp = NULL;
        uint16_t        fldid, locidtype, remidtype, mtype;
        struct hostent  *loche = NULL, *remhe = NULL;
        ike_ps_t        *psp = NULL;
        sadb_ident_t    *sidp;
        boolean_t       whacked = B_FALSE;
        int pfxlen = 0;

        if ((argv[c] == NULL) || (argv[c][0] != '{'))
                return (-1);
        if (argv[c][1] != 0) {
                /* no space between '{' and first token */
                argv[c]++;
        } else {
                c++;
        }
        if ((argv[argc - 1][strlen(argv[argc - 1]) - 1] == '}') &&
            (argv[argc - 1][0] != '}')) {
                /*
                 * whack '}' without a space before it or parsers break.
                 * Remember this trailing character for later
                 */
                argv[argc - 1][strlen(argv[argc - 1]) - 1] = '\0';
                whacked = B_TRUE;
        }

        /* Default to type IP */
        locidtype = remidtype = SADB_IDENTTYPE_RESERVED;
        /* Default to base exchanges */
        mtype = IKE_XCHG_BASE;

        while ((c < argc) && (argv[c] != NULL) && (argv[c][0] != '}')) {
                if ((argv[c + 1] == NULL) || (argv[c + 1][0] == '}'))
                        goto bail;
                if (parse_psfldid(argv[c++], &fldid) < 0)
                        goto bail;
                switch (fldid) {
                case PSFLD_LOCID:
                        locid = argv[c++];
                        locidlen = strlen(locid) + 1;
                        break;
                case PSFLD_LOCIDTYPE:
                        if (parse_idtype(argv[c++], &locidtype) < 0)
                                goto bail;
                        break;
                case PSFLD_REMID:
                        remid = argv[c++];
                        remidlen = strlen(remid) + 1;
                        break;
                case PSFLD_REMIDTYPE:
                        if (parse_idtype(argv[c++], &remidtype) < 0)
                                goto bail;
                        break;
                case PSFLD_MODE:
                        if (parse_ikmtype(argv[c++], &mtype) < 0)
                                goto bail;
                        break;
                case PSFLD_KEY:
                        keyp  = parse_key(argv[c++], &keylen, &keybits);
                        if (keyp == NULL)
                                goto bail;
                        break;
                }
        }

        /* Make sure the line was terminated with '}' */
        if (argv[c] == NULL) {
                if (!whacked)
                        goto bail;
        } else if (argv[c][0] != '}') {
                goto bail;
        }

        /*
         * make sure we got all the required fields.  If no idtype, assume
         * ip addr; if that translation fails, we'll catch the error then.
         */
        if (locid == NULL || remid == NULL || keyp == NULL || mtype == 0)
                goto bail;

        /* figure out the size buffer we need */
        *len = sizeof (ike_ps_t);
        if (locidtype != SADB_IDENTTYPE_RESERVED) {
                a_locidtotal = IKEDOORROUNDUP(sizeof (sadb_ident_t) + locidlen);
                *len += a_locidtotal;
        }
        if (remidtype != SADB_IDENTTYPE_RESERVED) {
                a_remidtotal = IKEDOORROUNDUP(sizeof (sadb_ident_t) + remidlen);
                *len += a_remidtotal;
        }
        *len += keylen;

        psp = malloc(*len);
        if (psp == NULL)
                goto bail;
        (void) memset(psp, 0, *len);

        psp->ps_ike_mode = mtype;

        psp->ps_localid_off = sizeof (ike_ps_t);
        if (locidtype == SADB_IDENTTYPE_RESERVED) {
                locpfx = strchr(locid, '/');
                if (locpfx != NULL) {
                        *locpfx = '\0';
                        locpfx++;
                }

                /*
                 * this is an ip address, store in the sockaddr field;
                 * we won't use an sadb_ident_t.
                 */
                psp->ps_localid_len = 0;
                if (parse_addr(1, &locid, &loche) < 0)
                        goto bail;
                if (loche->h_addr_list[1] != NULL) {
                        message(gettext("preshared key identifier cannot "
                            "match multiple IP addresses"));
                        goto bail;
                }
                headdr2sa(loche->h_addr_list[0], &psp->ps_ipaddrs.loc_addr,
                    loche->h_length);
                FREE_HE(loche);
        } else {
                psp->ps_localid_len = sizeof (sadb_ident_t) + locidlen;
                sidp = (sadb_ident_t *)((int)psp + psp->ps_localid_off);
                sidp->sadb_ident_len = psp->ps_localid_len;
                sidp->sadb_ident_type = locidtype;
                (void) strlcpy((char *)(sidp + 1), locid, a_locidtotal);
        }

        psp->ps_remoteid_off = psp->ps_localid_off + a_locidtotal;
        if (remidtype == SADB_IDENTTYPE_RESERVED) {
                rempfx = strchr(remid, '/');
                if (rempfx != NULL) {
                        *rempfx = '\0';
                        rempfx++;
                }

                /*
                 * this is an ip address, store in the sockaddr field;
                 * we won't use an sadb_ident_t.
                 */
                psp->ps_remoteid_len = 0;
                if (parse_addr(1, &remid, &remhe) < 0)
                        goto bail;
                if (remhe->h_addr_list[1] != NULL) {
                        message(gettext("preshared key identifier cannot "
                            "match multiple IP addresses"));
                        goto bail;
                }
                headdr2sa(remhe->h_addr_list[0], &psp->ps_ipaddrs.rem_addr,
                    remhe->h_length);
                FREE_HE(remhe);
        } else {
                /* make sure we have at least 16-bit alignment */
                if (remidlen & 0x1)
                        remidlen++;
                psp->ps_remoteid_len = sizeof (sadb_ident_t) + remidlen;
                sidp = (sadb_ident_t *)((int)psp + psp->ps_remoteid_off);
                sidp->sadb_ident_len = psp->ps_remoteid_len;
                sidp->sadb_ident_type = remidtype;
                (void) strlcpy((char *)(sidp + 1), remid, a_remidtotal);
        }

        psp->ps_key_off = psp->ps_remoteid_off + a_remidtotal;
        psp->ps_key_len = keylen;
        psp->ps_key_bits = keybits;
        (void) memcpy((uint8_t *)((int)psp + psp->ps_key_off), keyp, keylen);
        if (locpfx != NULL && ((pfxlen = atoi(locpfx)) > 0))
                psp->ps_localid_plen = pfxlen;
        if (rempfx != NULL && ((pfxlen = atoi(rempfx)) > 0))
                psp->ps_remoteid_plen = pfxlen;

        *presharedpp = psp;

        return (c);

bail:
        if (loche != NULL)
                FREE_HE(loche);
        if (remhe != NULL)
                FREE_HE(remhe);
        if (keyp != NULL)
                free(keyp);
        if (psp != NULL)
                free(psp);

        *presharedpp = NULL;

        return (-1);
}

/*
 * Printing functions
 *
 * A potential point of confusion here is that the ikeadm-specific string-
 * producing functions do not match the ipsec_util.c versions in style: the
 * ikeadm-specific functions return a string (and are named foostr), while
 * the ipsec_util.c functions actually print the string to the file named
 * in the second arg to the function (and are named dump_foo).
 *
 * Localization for ikeadm seems more straightforward when complete
 * phrases are translated rather than: a part of a phrase, a call to
 * dump_foo(), and more of the phrase.  It could also accommodate
 * non-English grammar more easily.
 */

static char *
errstr(int err)
{
        static char     rtn[MAXLINESIZE];

        switch (err) {
        case IKE_ERR_NO_OBJ:
                return (gettext("No data returned"));
        case IKE_ERR_NO_DESC:
                return (gettext("No destination provided"));
        case IKE_ERR_ID_INVALID:
                return (gettext("Id info invalid"));
        case IKE_ERR_LOC_INVALID:
                return (gettext("Destination invalid"));
        case IKE_ERR_CMD_INVALID:
                return (gettext("Command invalid"));
        case IKE_ERR_DATA_INVALID:
                return (gettext("Supplied data invalid"));
        case IKE_ERR_CMD_NOTSUP:
                return (gettext("Unknown command"));
        case IKE_ERR_REQ_INVALID:
                return (gettext("Request invalid"));
        case IKE_ERR_NO_PRIV:
                return (gettext("Not allowed at current privilege level"));
        case IKE_ERR_NO_AUTH:
                return (gettext("User not authorized"));
        case IKE_ERR_SYS_ERR:
                return (gettext("System error"));
        case IKE_ERR_DUP_IGNORED:
                return (gettext("One or more duplicate entries ignored"));
        case IKE_ERR_NO_TOKEN:
                return (gettext(
                    "token login failed or no objects on device"));
        case IKE_ERR_IN_PROGRESS:
                return (gettext(
                    "Duplicate operation already in progress"));
        case IKE_ERR_NO_MEM:
                return (gettext(
                    "Insufficient memory"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown error %d>"), err);
                return (rtn);
        }
}

static char *
dbgstr(int bit)
{
        static char     rtn[MAXLINESIZE];

        switch (bit) {
        case D_CERT:
                return (gettext("Certificate management"));
        case D_KEY:
                return (gettext("Key management"));
        case D_OP:
                return (gettext("Operational"));
        case D_P1:
                return (gettext("Phase 1 SA creation"));
        case D_P2:
                return (gettext("Phase 2 SA creation"));
        case D_PFKEY:
                return (gettext("PF_KEY interface"));
        case D_POL:
                return (gettext("Policy management"));
        case D_PROP:
                return (gettext("Proposal construction"));
        case D_DOOR:
                return (gettext("Door interface"));
        case D_CONFIG:
                return (gettext("Config file processing"));
        case D_LABEL:
                return (gettext("MAC label processing"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown flag 0x%x>"), bit);
                return (rtn);
        }
}

static char *
privstr(int priv)
{
        static char     rtn[MAXLINESIZE];

        switch (priv) {
        case IKE_PRIV_MINIMUM:
                return (gettext("base privileges"));
        case IKE_PRIV_MODKEYS:
                return (gettext("access to preshared key information"));
        case IKE_PRIV_KEYMAT:
                return (gettext("access to keying material"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown level %d>"), priv);
                return (rtn);
        }
}

static char *
xchgstr(int xchg)
{
        static char     rtn[MAXLINESIZE];

        switch (xchg) {
        case IKE_XCHG_NONE:
                return (gettext("<unspecified>"));
        case IKE_XCHG_BASE:
                return (gettext("base"));
        case IKE_XCHG_IDENTITY_PROTECT:
                return (gettext("main mode (identity protect)"));
        case IKE_XCHG_AUTH_ONLY:
                return (gettext("authentication only"));
        case IKE_XCHG_AGGRESSIVE:
                return (gettext("aggressive mode"));
        case IKE_XCHG_IP_AND_AGGR:
                return (gettext("main and aggressive mode"));
        case IKE_XCHG_ANY:
                return (gettext("any mode"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown %d>"), xchg);
                return (rtn);
        }
}

static char *
statestr(int state)
{
        static char     rtn[MAXLINESIZE];

        switch (state) {
        case IKE_SA_STATE_INIT:
                return (gettext("INITIALIZING"));
        case IKE_SA_STATE_SENT_SA:
                return (gettext("SENT FIRST MSG (SA)"));
        case IKE_SA_STATE_SENT_KE:
                return (gettext("SENT SECOND MSG (KE)"));
        case IKE_SA_STATE_SENT_LAST:
                return (gettext("SENT FINAL MSG"));
        case IKE_SA_STATE_DONE:
                return (gettext("ACTIVE"));
        case IKE_SA_STATE_DELETED:
                return (gettext("DELETED"));
        case IKE_SA_STATE_INVALID:
                return (gettext("<invalid>"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown %d>"), state);
                return (rtn);
        }
}

static char *
authmethstr(int meth)
{
        static char     rtn[MAXLINESIZE];

        switch (meth) {
        case IKE_AUTH_METH_PRE_SHARED_KEY:
                return (gettext("pre-shared key"));
        case IKE_AUTH_METH_DSS_SIG:
                return (gettext("DSS signatures"));
        case IKE_AUTH_METH_RSA_SIG:
                return (gettext("RSA signatures"));
        case IKE_AUTH_METH_RSA_ENCR:
                return (gettext("RSA Encryption"));
        case IKE_AUTH_METH_RSA_ENCR_REVISED:
                return (gettext("Revised RSA Encryption"));
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown %d>"), meth);
                return (rtn);
        }
}

static char *
prfstr(int prf)
{
        static char     rtn[MAXLINESIZE];

        switch (prf) {
        case IKE_PRF_NONE:
                return (gettext("<none/unavailable>"));
        case IKE_PRF_HMAC_MD5:
                return ("HMAC MD5");
        case IKE_PRF_HMAC_SHA1:
                return ("HMAC SHA1");
        case IKE_PRF_HMAC_SHA256:
                return ("HMAC SHA256");
        case IKE_PRF_HMAC_SHA384:
                return ("HMAC SHA384");
        case IKE_PRF_HMAC_SHA512:
                return ("HMAC SHA512");
        default:
                (void) snprintf(rtn, MAXLINESIZE,
                    gettext("<unknown %d>"), prf);
                return (rtn);
        }
}

static char *
dhstr(int grp)
{
        static char     rtn[MAXLINESIZE];

        switch (grp) {
        case 0:
                return (gettext("<unavailable>"));
        case IKE_GRP_DESC_MODP_768:
                return (gettext("768-bit MODP (group 1)"));
        case IKE_GRP_DESC_MODP_1024:
                return (gettext("1024-bit MODP (group 2)"));
        case IKE_GRP_DESC_EC2N_155:
                return (gettext("EC2N group on GP[2^155]"));
        case IKE_GRP_DESC_EC2N_185:
                return (gettext("EC2N group on GP[2^185]"));
        case IKE_GRP_DESC_MODP_1536:
                return (gettext("1536-bit MODP (group 5)"));
        case IKE_GRP_DESC_MODP_2048:
                return (gettext("2048-bit MODP (group 14)"));
        case IKE_GRP_DESC_MODP_3072:
                return (gettext("3072-bit MODP (group 15)"));
        case IKE_GRP_DESC_MODP_4096:
                return (gettext("4096-bit MODP (group 16)"));
        case IKE_GRP_DESC_MODP_6144:
                return (gettext("6144-bit MODP (group 17)"));
        case IKE_GRP_DESC_MODP_8192:
                return (gettext("8192-bit MODP (group 18)"));
        case IKE_GRP_DESC_ECP_256:
                return (gettext("256-bit ECP (group 19)"));
        case IKE_GRP_DESC_ECP_384:
                return (gettext("384-bit ECP (group 20)"));
        case IKE_GRP_DESC_ECP_521:
                return (gettext("521-bit ECP (group 21)"));
        case IKE_GRP_DESC_MODP_1024_160:
                return (
                    gettext("1024-bit MODP with 160-bit subprime (group 22)"));
        case IKE_GRP_DESC_MODP_2048_224:
                return (
                    gettext("2048-bit MODP with 224-bit subprime (group 23)"));
        case IKE_GRP_DESC_MODP_2048_256:
                return (
                    gettext("2048-bit MODP with 256-bit subprime (group 24)"));
        case IKE_GRP_DESC_ECP_192:
                return (gettext("192-bit ECP (group 25)"));
        case IKE_GRP_DESC_ECP_224:
                return (gettext("224-bit ECP (group 26)"));
        default:
                (void) snprintf(rtn, MAXLINESIZE, gettext("<unknown %d>"), grp);
                return (rtn);
        }
}

static void
print_hdr(char *prefix, ike_p1_hdr_t *hdrp)
{
        char sbuf[TBUF_SIZE];
        char tbuf[TBUF_SIZE];
        time_t ltime = (time_t)hdrp->p1hdr_dpd_time;

        (void) printf(
            gettext("%s Cookies: Initiator 0x%llx  Responder 0x%llx\n"),
            prefix, ntohll(hdrp->p1hdr_cookies.cky_i),
            ntohll(hdrp->p1hdr_cookies.cky_r));
        (void) printf(gettext("%s The local host is the %s.\n"), prefix,
            hdrp->p1hdr_isinit ? gettext("initiator") : gettext("responder"));
        (void) printf(gettext("%s ISAKMP version %d.%d; %s exchange\n"), prefix,
            hdrp->p1hdr_major, hdrp->p1hdr_minor, xchgstr(hdrp->p1hdr_xchg));
        (void) printf(gettext("%s Current state is %s\n"), prefix,
            statestr(hdrp->p1hdr_state));
        if (hdrp->p1hdr_support_dpd == B_FALSE) {
                return;
        }
        (void) printf(gettext("%s Dead Peer Detection (RFC 3706)"
            " enabled"), prefix);
        if (hdrp->p1hdr_dpd_state < DPD_IN_PROGRESS) {
                (void) printf("\n");
                return;
        }
        if (strftime(tbuf, TBUF_SIZE, NULL,
            localtime(&ltime)) == 0) {
                (void) strlcpy(tbuf, gettext("<time conversion failed>"),
                    TBUF_SIZE);
        }
        (void) printf(gettext("\n%s Dead Peer Detection handshake "), prefix);
        switch (hdrp->p1hdr_dpd_state) {
        case DPD_SUCCESSFUL:
                (void) strlcpy(sbuf, gettext("was successful at "), TBUF_SIZE);
                break;
        case DPD_FAILURE:
                (void) strlcpy(sbuf, gettext("failed at "), TBUF_SIZE);
                break;
        case DPD_IN_PROGRESS:
                (void) strlcpy(sbuf, gettext("is in progress."), TBUF_SIZE);
                break;
        }
        (void) printf("%s %s", sbuf,
            (hdrp->p1hdr_dpd_state == DPD_IN_PROGRESS) ? "" : tbuf);
        (void) printf("\n");
}

static void
print_lt_limits(char *prefix, ike_p1_xform_t *xfp)
{
        char byte_str[BYTE_STR_SIZE]; /* byte lifetime string representation */
        char secs_str[SECS_STR_SIZE]; /* lifetime string representation */

        (void) printf(gettext("%s Lifetime limits:\n"), prefix);
        (void) printf(gettext("%s %u seconds%s; %u kbytes %sprotected\n"),
            prefix, xfp->p1xf_max_secs, secs2out(xfp->p1xf_max_secs,
            secs_str, sizeof (secs_str), SPC_BEGIN), xfp->p1xf_max_kbytes,
            bytecnt2out((uint64_t)xfp->p1xf_max_kbytes << 10, byte_str,
            sizeof (byte_str), SPC_END));
        (void) printf(gettext("%s keying material for IPsec SAs can be "
            "provided %u times%s\n"), prefix, xfp->p1xf_max_keyuses,
            xfp->p1xf_max_keyuses == 0 ? " (no limit)" : "");
}

#define LT_USAGE_LEN    16      /* 1 uint64 + 2 uint32s */
static void
print_lt_usage(char *prefix, ike_p1_stats_t *sp)
{
        time_t  scratch;
        char    tbuf[TBUF_SIZE];
        char    bytestr[BYTE_STR_SIZE]; /* byte lifetime representation */

        (void) printf(gettext("%s Current usage:\n"), prefix);
        scratch = (time_t)sp->p1stat_start;
        if (strftime(tbuf, TBUF_SIZE, NULL, localtime(&scratch)) == 0)
                (void) strlcpy(tbuf, gettext("<time conversion failed>"),
                    TBUF_SIZE);
        (void) printf(gettext("%s SA was created at %s\n"), prefix, tbuf);
        (void) printf(gettext("%s %u kbytes %sprotected\n"),
            prefix, sp->p1stat_kbytes,
            bytecnt2out((uint64_t)sp->p1stat_kbytes << 10, bytestr,
            sizeof (bytestr), SPC_END));
        (void) printf(gettext("%s keying material for IPsec SAs provided "
            "%u times\n"), prefix, sp->p1stat_keyuses);
}

static void
print_xform(char *prefix, ike_p1_xform_t *xfp, boolean_t print_lifetimes)
{
        (void) printf(gettext("%s Authentication method: %s"), prefix,
            authmethstr(xfp->p1xf_auth_meth));
        (void) printf(gettext("\n%s Encryption alg: "), prefix);
        (void) dump_ealg(xfp->p1xf_encr_alg, stdout);
        if (xfp->p1xf_encr_low_bits != 0) {
                (void) printf(gettext("(%d..%d)"), xfp->p1xf_encr_low_bits,
                    xfp->p1xf_encr_high_bits);
        } else if ((xfp->p1xf_encr_low_bits == 0) &&
            (xfp->p1xf_encr_high_bits != 0)) {
                /*
                 * High bits is a placeholder for
                 * negotiated algorithm strength
                 */
                (void) printf(gettext("(%d)"), xfp->p1xf_encr_high_bits);
        }
        (void) printf(gettext("; Authentication alg: "));
        (void) dump_aalg(xfp->p1xf_auth_alg, stdout);
        (void) printf("\n%s ", prefix);
        if (xfp->p1xf_prf != 0)
                (void) printf(gettext("PRF: %s ; "), prfstr(xfp->p1xf_prf));
        (void) printf(gettext("Oakley Group: %s\n"),
            dhstr(xfp->p1xf_dh_group));
        if (xfp->p1xf_pfs == 0) {
                (void) printf(gettext("%s Phase 2 PFS is not used\n"), prefix);
        } else {
                (void) printf(gettext(
                    "%s Phase 2 PFS is required (Oakley Group: %s)\n"),
                    prefix, dhstr(xfp->p1xf_pfs));
        }

        if (print_lifetimes)
                print_lt_limits(prefix, xfp);
}

static void
print_lifetime(char *prefix, ike_p1_xform_t *xfp, ike_p1_stats_t *sp,
    int statlen)
{
        time_t  current, remain, exp;
        char    tbuf[TBUF_SIZE];
        char    byte_str[BYTE_STR_SIZE]; /* byte lifetime representation */
        char    secs_str[SECS_STR_SIZE]; /* seconds lifetime representation */

        current = time(NULL);

        print_lt_limits(prefix, xfp);

        /*
         * make sure the stats struct we've been passed is as big
         * as we expect it to be.  The usage stats are at the end,
         * so anything less than the size we expect won't work.
         */
        if (statlen >= sizeof (ike_p1_stats_t)) {
                print_lt_usage(prefix, sp);
        } else {
                return;
        }

        (void) printf(gettext("%s Expiration info:\n"), prefix);

        if (xfp->p1xf_max_kbytes != 0)
                (void) printf(gettext("%s %u more bytes %scan be "
                    "protected.\n"),
                    prefix, xfp->p1xf_max_kbytes - sp->p1stat_kbytes,
                    bytecnt2out((uint64_t)(xfp->p1xf_max_kbytes -
                    sp->p1stat_kbytes) << 10, byte_str, sizeof (byte_str),
                    SPC_END));

        if (xfp->p1xf_max_keyuses != 0)
                (void) printf(gettext("%s Keying material can be provided "
                    "%u more times.\n"), prefix,
                    xfp->p1xf_max_keyuses - sp->p1stat_keyuses);

        if (xfp->p1xf_max_secs != 0) {
                exp = (time_t)sp->p1stat_start + (time_t)xfp->p1xf_max_secs;
                remain = exp - current;
                if (strftime(tbuf, TBUF_SIZE, NULL, localtime(&exp)) == 0)
                        (void) strlcpy(tbuf,
                            gettext("<time conversion failed>"), TBUF_SIZE);
                /*
                 * The SA may have expired but still exist because libike
                 * has not freed it yet.
                 */
                if (remain > 0) {
                        (void) printf(gettext(
                            "%s SA expires in %lu seconds%s\n"),
                            prefix, remain, secs2out(remain, secs_str,
                            sizeof (secs_str), SPC_BEGIN));
                        (void) printf(gettext("%s Time of expiration: %s\n"),
                            prefix, tbuf);
                } else {
                        (void) printf(gettext("%s SA Expired at %s\n"),
                            prefix, tbuf);
                }
        }
}

/* used to verify structure lengths... */
#define COUNTER_32BIT   4
#define COUNTER_PAIR    8

static void
print_p1stats(char *prefix, ike_p1_stats_t *sp, int statlen,
    boolean_t print_lifetimes)
{
        if (statlen < COUNTER_PAIR)
                return;
        (void) printf(gettext("%s %u Quick Mode SAs created; "), prefix,
            sp->p1stat_new_qm_sas);
        (void) printf(gettext("%u Quick Mode SAs deleted\n"),
            sp->p1stat_del_qm_sas);
        statlen -= COUNTER_PAIR;

        if ((print_lifetimes) && (statlen >= LT_USAGE_LEN))
                print_lt_usage(prefix, sp);
}

static void
print_errs(char *prefix, ike_p1_errors_t *errp, int errlen)
{
        /*
         * Don't try to break this one up; it's either all or nothing!
         */
        if (errlen < sizeof (ike_p1_errors_t))
                return;

        (void) printf(gettext("%s %u RX errors: "), prefix,
            errp->p1err_decrypt + errp->p1err_hash + errp->p1err_otherrx);
        (void) printf(gettext("%u decryption, %u hash, %u other\n"),
            errp->p1err_decrypt, errp->p1err_hash, errp->p1err_otherrx);
        (void) printf(gettext("%s %u TX errors\n"), prefix, errp->p1err_tx);
}

static void
print_addr_range(char *prefix, ike_addr_pr_t *pr)
{
        boolean_t       range = B_TRUE;
        struct sockaddr_storage *beg, *end;
        struct sockaddr_in      *bsin, *esin;
        struct sockaddr_in6     *bsin6, *esin6;

        beg = &pr->beg_iprange;
        end = &pr->end_iprange;

        if (beg->ss_family != end->ss_family) {
                (void) printf(gettext("%s invalid address range\n"), prefix);
                return;
        }

        switch (beg->ss_family) {
        case AF_INET:
                bsin = (struct sockaddr_in *)beg;
                esin = (struct sockaddr_in *)end;
                if ((uint32_t)bsin->sin_addr.s_addr ==
                    (uint32_t)esin->sin_addr.s_addr)
                        range = B_FALSE;
                break;
        case AF_INET6:
                bsin6 = (struct sockaddr_in6 *)beg;
                esin6 = (struct sockaddr_in6 *)end;
                if (IN6_ARE_ADDR_EQUAL(&bsin6->sin6_addr, &esin6->sin6_addr))
                        range = B_FALSE;
                break;
        default:
                (void) printf(gettext("%s invalid address range\n"), prefix);
                return;
        }

        (void) printf("%s ", prefix);
        (void) dump_sockaddr((struct sockaddr *)beg, 0, B_TRUE, stdout, nflag);
        if (range) {
                (void) printf(" - ");
                (void) dump_sockaddr((struct sockaddr *)end, 0, B_TRUE, stdout,
                    nflag);
        }
        (void) printf("\n");

}

/*
 * used to tell printing function if info should be identified
 * as belonging to initiator, responder, or neither
 */
#define IS_INITIATOR    1
#define IS_RESPONDER    2
#define DONT_PRINT_INIT 3

static void
print_addr(char *prefix, struct sockaddr_storage *sa, int init_instr,
    int mask)
{
        (void) printf(gettext("%s Address"), prefix);

        if (init_instr != DONT_PRINT_INIT)
                (void) printf(" (%s):\n", (init_instr == IS_INITIATOR) ?
                    gettext("Initiator") : gettext("Responder"));
        else
                (void) printf(":\n");

        (void) printf("%s ", prefix);
        (void) dump_sockaddr((struct sockaddr *)sa, mask, B_FALSE, stdout,
            nflag);
}

static void
print_id(char *prefix, sadb_ident_t *idp, int init_instr)
{
        boolean_t       canprint;

        switch (init_instr) {
        case IS_INITIATOR:
                (void) printf(gettext("%s Initiator identity, "), prefix);
                break;
        case IS_RESPONDER:
                (void) printf(gettext("%s Responder identity, "), prefix);
                break;
        case DONT_PRINT_INIT:
                (void) printf(gettext("%s Identity, "), prefix);
                break;
        default:
                (void) printf(gettext("<invalid identity>\n"));
                return;
        }
        (void) printf(gettext("uid=%d, type "), idp->sadb_ident_id);
        canprint = dump_sadb_idtype(idp->sadb_ident_type, stdout, NULL);
        if (canprint) {
                (void) printf("\n%s %s\n", prefix, (char *)(idp + 1));
        } else {
                (void) printf(gettext("\n%s "), prefix);
                print_asn1_name(stdout,
                    (const unsigned char *)(idp + 1),
                    SADB_64TO8(idp->sadb_ident_len) - sizeof (sadb_ident_t));
        }
}

static void
print_idspec(char *prefix, char *idp, int icnt, int ecnt)
{
        int     i;

        (void) printf(gettext("%s Identity descriptors:\n"), prefix);

        for (i = 0; i < icnt; i++) {
                if (i == 0)
                        (void) printf(gettext("%s Includes:\n"), prefix);
                (void) printf("%s    %s\n", prefix, idp);
                idp += strlen(idp) + 1;
        }

        for (i = 0; i < ecnt; i++) {
                if (i == 0)
                        (void) printf(gettext("%s Excludes:\n"), prefix);
                (void) printf("%s    %s\n", prefix, idp);
                idp += strlen(idp) + 1;
        }
}

static void
print_keys(char *prefix, ike_p1_key_t *keyp, int size)
{
        uint32_t        *curp;
        ike_p1_key_t    *p;
        int             ssize;

        curp = (uint32_t *)keyp;

        ssize = sizeof (ike_p1_key_t);

        while ((intptr_t)curp - (intptr_t)keyp < size) {
                size_t p1klen, len;

                p = (ike_p1_key_t *)curp;
                p1klen = p->p1key_len;
                len = p1klen - ssize;

                p1klen = roundup(p1klen, sizeof (ike_p1_key_t));
                if (p1klen < ssize) {
                        (void) printf(gettext("Short key\n"));
                        break;
                }

                switch (p->p1key_type) {
                case IKE_KEY_PRESHARED:
                        (void) printf(gettext("%s Pre-shared key (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_SKEYID:
                        (void) printf(gettext("%s SKEYID (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_SKEYID_D:
                        (void) printf(gettext("%s SKEYID_d (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_SKEYID_A:
                        (void) printf(gettext("%s SKEYID_a (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_SKEYID_E:
                        (void) printf(gettext("%s SKEYID_e (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_ENCR:
                        (void) printf(gettext("%s Encryption key (%d bytes): "),
                            prefix, len);
                        break;
                case IKE_KEY_IV:
                        (void) printf(
                            gettext("%s Initialization vector (%d bytes): "),
                            prefix, len);
                        break;
                default:
                        (void) printf(gettext("%s Unidentified key info %p %d"),
                            prefix, p, p1klen);
                        goto badkey;
                }
                (void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
                    stdout, B_FALSE);
badkey:
                (void) printf("\n");
                assert(IS_P2ALIGNED(p1klen, 8));
                curp += (p1klen >> 2);
        }
}

static void
print_group_header(void)
{
        (void) printf(gettext("\nList of Diffie-Hellman groups for setting "
            "up IKE SAs"));
        (void) printf(gettext("\nThe values match the IPsec attribute "
            "assigned numbers published by IANA\n\n"));
        (void) printf("%-6s%-9s%-50s\n",
            gettext("Value"), gettext("Strength"), gettext("Description"));
}

static void
print_group(ike_group_t *gp)
{
        (void) printf("%-6u%-9u%-50s\n",
            gp->group_number, gp->group_bits, gp->group_label);
}

static void
print_encralg_header(void)
{
        (void) printf(gettext("\nList of encryption algorithms for IKE"));
        (void) printf(gettext("\nThe values match the IPsec attribute "
            "assigned numbers published by IANA\n\n"));
        (void) printf("%-6s%-20s%-15s\n", gettext("Value"),
            gettext("Name"), gettext("Keylen range"));
}

static void
print_encralg(ike_encralg_t *ep)
{
        char keylen_str[16];

        (void) strlcpy(keylen_str, "N/A", sizeof (keylen_str));
        if (ep->encr_keylen_min != 0 || ep->encr_keylen_max != 0)
                (void) snprintf(keylen_str, sizeof (keylen_str), "%d-%d",
                    ep->encr_keylen_min, ep->encr_keylen_max);
        (void) printf("%-6u%-20s%-15s\n",
            ep->encr_value, ep->encr_name, keylen_str);
}

static void
print_authalg_header(void)
{
        (void) printf(gettext("\nList of authentication algorithms for IKE"));
        (void) printf(gettext("\nThe values match the IPsec attribute "
            "assigned numbers published by IANA\n\n"));
        (void) printf("%-6s%-20s\n", gettext("Value"), gettext("Name"));
}

static void
print_authalg(ike_authalg_t *ap)
{
        (void) printf("%-6u%-20s\n",
            ap->auth_value, ap->auth_name);
}

static void
print_p1(ike_p1_sa_t *p1)
{
        ike_p1_stats_t  *sp;
        ike_p1_errors_t *ep;
        ike_p1_key_t    *kp;
        sadb_ident_t    *lidp, *ridp;
        int             lstat, rstat;

        (void) printf("\n");
        print_hdr("IKESA:", &p1->p1sa_hdr);
        print_xform("XFORM:", &p1->p1sa_xform, B_FALSE);

        if (p1->p1sa_hdr.p1hdr_isinit) {
                lstat = IS_INITIATOR;
                rstat = IS_RESPONDER;
        } else {
                lstat = IS_RESPONDER;
                rstat = IS_INITIATOR;
        }
        print_addr("LOCIP:", &p1->p1sa_ipaddrs.loc_addr, lstat, 0);
        print_addr("REMIP:", &p1->p1sa_ipaddrs.rem_addr, rstat, 0);

        /*
         * the stat len might be 0; but still make the call
         * to print_lifetime() to pick up the xform info
         */
        sp = (ike_p1_stats_t *)((int)(p1) + p1->p1sa_stat_off);
        print_lifetime("LIFTM:", &p1->p1sa_xform, sp, p1->p1sa_stat_len);

        if (p1->p1sa_stat_len > 0) {
                print_p1stats("STATS:", sp, p1->p1sa_stat_len, B_FALSE);
        }

        if (p1->p1sa_error_len > 0) {
                ep = (ike_p1_errors_t *)((int)(p1) + p1->p1sa_error_off);
                print_errs("ERRS: ", ep, p1->p1sa_error_len);
        }

        if (p1->p1sa_localid_len > 0) {
                lidp = (sadb_ident_t *)((int)(p1) + p1->p1sa_localid_off);
                print_id("LOCID:", lidp, lstat);
        }

        if (p1->p1sa_remoteid_len > 0) {
                ridp = (sadb_ident_t *)((int)(p1) + p1->p1sa_remoteid_off);
                print_id("REMID:", ridp, rstat);
        }

        if (p1->p1sa_key_len > 0) {
                kp = (ike_p1_key_t *)((int)(p1) + p1->p1sa_key_off);
                print_keys("KEY:  ", kp, p1->p1sa_key_len);
        }
}

static void
print_certcache(ike_certcache_t *c)
{
        (void) printf("\n");

        (void) printf(gettext("CERTIFICATE CACHE ID: %d\n"), c->cache_id);
        (void) printf(gettext("\tSubject Name: <%s>\n"),
            (*c->subject != '\0') ? c->subject : gettext("Name unavailable"));
        (void) printf(gettext("\t Issuer Name: <%s>\n"),
            (*c->issuer != '\0') ? c->issuer : gettext("Name unavailable"));
        if ((int)c->certclass == -1)
                (void) printf(gettext("\t\t[trusted certificate]\n"));
        switch (c->linkage) {
        case CERT_OFF_WIRE:
                (void) printf(gettext("\t\t[Public certificate only]\n"));
                (void) printf(gettext(
                    "\t\t[Obtained via certificate payload]\n"));
                break;
        case CERT_NO_PRIVKEY:
                (void) printf(gettext("\t\t[Public certificate only]\n"));
                break;
        case CERT_PRIVKEY_LOCKED:
                (void) printf(gettext(
                    "\t\t[Private key linked but locked]\n"));
                break;
        case CERT_PRIVKEY_AVAIL:
                (void) printf(gettext("\t\t[Private key available]\n"));
                break;
        }
}

static void
print_ps(ike_ps_t *ps)
{
        sadb_ident_t    *lidp, *ridp;
        uint8_t         *keyp;

        (void) printf("\n");

        (void) printf(gettext("PSKEY: For %s exchanges\n"),
            xchgstr(ps->ps_ike_mode));

        if (ps->ps_key_len > 0) {
                keyp = (uint8_t *)((int)(ps) + ps->ps_key_off);
                (void) printf(gettext("PSKEY: Pre-shared key (%d bytes): "),
                    ps->ps_key_len);
                (void) dump_key(keyp, ps->ps_key_bits, 0, stdout, B_FALSE);
                (void) printf("\n");
        }

        /*
         * We get *either* and address or an ident, never both.  So if
         * the ident is there, don't try printing an address.
         */
        if (ps->ps_localid_len > 0) {
                lidp = (sadb_ident_t *)
                    ((int)(ps) + ps->ps_localid_off);
                print_id("LOCID:", lidp, DONT_PRINT_INIT);
        } else {
                print_addr("LOCIP:", &ps->ps_ipaddrs.loc_addr, DONT_PRINT_INIT,
                    ps->ps_localid_plen > 0 ? ps->ps_localid_plen : 0);
        }

        if (ps->ps_remoteid_len > 0) {
                ridp = (sadb_ident_t *)
                    ((int)(ps) + ps->ps_remoteid_off);
                print_id("REMID:", ridp, DONT_PRINT_INIT);
        } else {
                print_addr("REMIP:", &ps->ps_ipaddrs.rem_addr, DONT_PRINT_INIT,
                    ps->ps_remoteid_plen > 0 ? ps->ps_remoteid_plen : 0);
        }
}

#define PREFIXLEN       16

static void
print_rule(ike_rule_t *rp)
{
        char            prefix[PREFIXLEN];
        int             i;
        ike_p1_xform_t  *xfp;
        ike_addr_pr_t   *lipp, *ripp;
        char            *lidp, *ridp;
        char byte_str[BYTE_STR_SIZE]; /* kbyte string representation */
        char secs_str[SECS_STR_SIZE]; /* seconds string representation */

        (void) printf("\n");
        (void) printf(gettext("GLOBL: Label '%s', key manager cookie %u\n"),
            rp->rule_label, rp->rule_kmcookie);
        (void) printf(gettext("GLOBL: local_idtype="));
        (void) dump_sadb_idtype(rp->rule_local_idtype, stdout, NULL);
        (void) printf(gettext(", ike_mode=%s\n"), xchgstr(rp->rule_ike_mode));
        (void) printf(gettext(
            "GLOBL: p1_nonce_len=%u, p2_nonce_len=%u, p2_pfs=%s (group %u)\n"),
            rp->rule_p1_nonce_len, rp->rule_p2_nonce_len,
            (rp->rule_p2_pfs) ? gettext("true") : gettext("false"),
            rp->rule_p2_pfs);
        (void) printf(
            gettext("GLOBL: p2_lifetime=%u seconds%s\n"),
            rp->rule_p2_lifetime_secs, secs2out(rp->rule_p2_lifetime_secs,
            secs_str, sizeof (secs_str), SPC_BEGIN));
        (void) printf(
            gettext("GLOBL: p2_softlife=%u seconds%s\n"),
            rp->rule_p2_softlife_secs, secs2out(rp->rule_p2_softlife_secs,
            secs_str, sizeof (secs_str), SPC_BEGIN));
        (void) printf(
            gettext("GLOBL: p2_idletime=%u seconds%s\n"),
            rp->rule_p2_idletime_secs, secs2out(rp->rule_p2_idletime_secs,
            secs_str, sizeof (secs_str), SPC_BEGIN));
        /*
         * Perform explicit conversion before passing to bytecnt2out()
         * to avoid integer overflow.
         */
        (void) printf(
            gettext("GLOBL: p2_lifetime_kb=%u kilobytes%s\n"),
            rp->rule_p2_lifetime_kb,
            bytecnt2out((uint64_t)(rp->rule_p2_lifetime_kb) << 10,
            byte_str, sizeof (byte_str), SPC_BEGIN));
        (void) printf(
            gettext("GLOBL: p2_softlife_kb=%u kilobytes%s\n"),
            rp->rule_p2_softlife_kb,
            bytecnt2out(((uint64_t)(rp->rule_p2_softlife_kb)) << 10,
            byte_str, sizeof (byte_str), SPC_BEGIN));

        if (rp->rule_locip_cnt > 0) {
                (void) printf(gettext("LOCIP: IP address range(s):\n"));
                lipp = (ike_addr_pr_t *)((int)rp + rp->rule_locip_off);
                for (i = 0; i < rp->rule_locip_cnt; i++, lipp++) {
                        print_addr_range("LOCIP:", lipp);
                }
        }

        if (rp->rule_remip_cnt > 0) {
                (void) printf(gettext("REMIP: IP address range(s):\n"));
                ripp = (ike_addr_pr_t *)((int)rp + rp->rule_remip_off);
                for (i = 0; i < rp->rule_remip_cnt; i++, ripp++) {
                        print_addr_range("REMIP:", ripp);
                }
        }

        if (rp->rule_locid_inclcnt + rp->rule_locid_exclcnt > 0) {
                lidp = (char *)((int)rp + rp->rule_locid_off);
                print_idspec("LOCID:", lidp, rp->rule_locid_inclcnt,
                    rp->rule_locid_exclcnt);
        }

        if (rp->rule_remid_inclcnt + rp->rule_remid_exclcnt > 0) {
                ridp = (char *)((int)rp + rp->rule_remid_off);
                print_idspec("REMID:", ridp, rp->rule_remid_inclcnt,
                    rp->rule_remid_exclcnt);
        }

        if (rp->rule_xform_cnt > 0) {
                (void) printf(gettext("XFRMS: Available Transforms:\n"));
                xfp = (ike_p1_xform_t *)((int)rp +  rp->rule_xform_off);
                for (i = 0; i < rp->rule_xform_cnt; i++, xfp++) {
                        (void) snprintf(prefix, PREFIXLEN, "XF %2u:", i);
                        print_xform(prefix, xfp, B_TRUE);
                }
        }
}

#undef  PREFIXLEN

#define PRSACNTS(init, resp) \
                (void) printf(gettext("initiator: %10u   responder: %10u\n"), \
                    (init), (resp))

static void
print_stats(ike_stats_t *sp, int len)
{
        /*
         * before printing each line, make sure the structure we were
         * given is big enough to include the fields needed.
         */
        if (len < COUNTER_PAIR)
                return;
        (void) printf(gettext("Phase 1 SA counts:\n"));
        (void) printf(gettext("Current:   "));
        PRSACNTS(sp->st_init_p1_current, sp->st_resp_p1_current);
        len -= COUNTER_PAIR;

        if (len < COUNTER_PAIR)
                return;
        (void) printf(gettext("Total:     "));
        PRSACNTS(sp->st_init_p1_total, sp->st_resp_p1_total);
        len -= COUNTER_PAIR;

        if (len < COUNTER_PAIR)
                return;
        (void) printf(gettext("Attempted: "));
        PRSACNTS(sp->st_init_p1_attempts, sp->st_resp_p1_attempts);
        len -= COUNTER_PAIR;

        if (len < (COUNTER_PAIR + COUNTER_32BIT))
                return;
        (void) printf(gettext("Failed:    "));
        PRSACNTS(sp->st_init_p1_noresp + sp->st_init_p1_respfail,
            sp->st_resp_p1_fail);
        (void) printf(
            gettext("           initiator fails include %u time-out(s)\n"),
            sp->st_init_p1_noresp);

        if (len < PATH_MAX)
                return;
        if (*(sp->st_pkcs11_libname) != '\0')
                (void) printf(gettext("PKCS#11 library linked in from %s\n"),
                    sp->st_pkcs11_libname);
}

/* Print one line of 'get defaults' output (i.e. single value). */
static void
print_defaults(char *label, char *description, char *unit,
    uint_t current, uint_t def)
{
        (void) printf("%-18s%-10s%11u %-10s%-26s\n", label,
            (current != def) ? gettext("config") : gettext("default"),
            current, unit, description);
}

/*
 * Print out defaults used by in.iked, the argument is a buffer containing
 * two ike_defaults_t's, the first contains the hard coded defaults, the second
 * contains the actual values used. If these differ, then the defaults have been
 * changed via a config file entry. Note that "-" indicates this default
 * is not tunable via ike.config(5) or is system wide tunable.
 */
static void
do_print_defaults(ike_defaults_t *dp)
{
        ike_defaults_t *ddp;
        ddp = (ike_defaults_t *)(dp + 1);

        (void) printf(gettext("\nGlobal defaults. Some values can be"
            " over-ridden on a per rule basis.\n"));
        (void) printf(gettext("\nSystem defaults are time delayed.\n\n"));

        (void) printf("%-18s%-10s%-12s%-10s%-26s\n\n",
            gettext("Token:"), gettext("Source:"), gettext("Value:"),
            gettext("Unit:"), gettext("Description:"));

        /* iked tunables */
        print_defaults("p1_lifetime_secs", gettext("phase 1 lifetime"),
            gettext("seconds"), ddp->rule_p1_lifetime_secs,
            dp->rule_p1_lifetime_secs);

        print_defaults("-", gettext("minimum phase 1 lifetime"),
            gettext("seconds"), ddp->rule_p1_minlife,
            dp->rule_p1_minlife);

        print_defaults("p1_nonce_len", gettext("phase 1 nonce length"),
            gettext("bytes"), ddp->rule_p1_nonce_len,
            dp->rule_p1_nonce_len);

        print_defaults("p2_lifetime_secs", gettext("phase 2 lifetime"),
            gettext("seconds"), ddp->rule_p2_lifetime_secs,
            dp->rule_p2_lifetime_secs);

        print_defaults("p2_softlife_secs", gettext("phase 2 soft lifetime"),
            gettext("seconds"), ddp->rule_p2_softlife_secs,
            dp->rule_p2_softlife_secs);

        print_defaults("p2_idletime_secs", gettext("phase 2 idle time"),
            gettext("seconds"), ddp->rule_p2_idletime_secs,
            dp->rule_p2_idletime_secs);

        print_defaults("p2_lifetime_kb", gettext("phase 2 lifetime"),
            gettext("kilobytes"), ddp->rule_p2_lifetime_kb,
            dp->rule_p2_lifetime_kb);

        print_defaults("p2_softlife_kb", gettext("phase 2 soft lifetime"),
            gettext("kilobytes"), ddp->rule_p2_softlife_kb,
            dp->rule_p2_softlife_kb);

        /* system wide tunables */
        print_defaults("-", gettext("system phase 2 lifetime"),
            gettext("seconds"), ddp->sys_p2_lifetime_secs,
            dp->sys_p2_lifetime_secs);

        print_defaults("-", gettext("system phase 2 soft lifetime"),
            gettext("seconds"), ddp->sys_p2_softlife_secs,
            dp->sys_p2_softlife_secs);

        print_defaults("-", gettext("system phase 2 idle time"),
            gettext("seconds"), ddp->sys_p2_idletime_secs,
            dp->sys_p2_idletime_secs);

        print_defaults("-", gettext("system phase 2 lifetime"),
            gettext("bytes"), ddp->sys_p2_lifetime_bytes,
            dp->sys_p2_lifetime_bytes);

        print_defaults("-", gettext("system phase 2 soft lifetime"),
            gettext("bytes"), ddp->sys_p2_softlife_bytes,
            dp->sys_p2_softlife_bytes);

        /* minimum and maximum values */
        print_defaults("-", gettext("minimum phase 2 hard lifetime"),
            gettext("seconds"), ddp->rule_p2_minlife_hard_secs,
            dp->rule_p2_minlife_hard_secs);

        print_defaults("-", gettext("minimum phase 2 soft lifetime"),
            gettext("seconds"), ddp->rule_p2_minlife_soft_secs,
            dp->rule_p2_minlife_soft_secs);

        print_defaults("-", gettext("minimum phase 2 idle lifetime"),
            gettext("seconds"), ddp->rule_p2_minlife_idle_secs,
            dp->rule_p2_minlife_idle_secs);

        print_defaults("-", gettext("minimum phase 2 hard lifetime"),
            gettext("kilobytes"), ddp->rule_p2_minlife_hard_kb,
            dp->rule_p2_minlife_hard_kb);

        print_defaults("-", gettext("minimum phase 2 soft lifetime"),
            gettext("kilobytes"), ddp->rule_p2_minlife_soft_kb,
            dp->rule_p2_minlife_soft_kb);

        print_defaults("-", gettext("minimum phase 2 delta"),
            gettext("seconds"), ddp->rule_p2_mindiff_secs,
            dp->rule_p2_mindiff_secs);

        print_defaults("-", gettext("minimum phase 2 delta"),
            gettext("kilobytes"), ddp->rule_p2_mindiff_kb,
            dp->rule_p2_mindiff_kb);

        print_defaults("-", gettext("maximum phase 2 lifetime"),
            gettext("seconds"), ddp->rule_p2_maxlife_secs,
            dp->rule_p2_maxlife_secs);

        print_defaults("-", gettext("conversion factor"),
            gettext("kbytes/s"), ddp->conversion_factor,
            dp->conversion_factor);

        print_defaults("-", gettext("maximum phase 2 lifetime"),
            gettext("kilobytes"), ddp->rule_p2_maxlife_kb,
            dp->rule_p2_maxlife_kb);

        /* other values */
        print_defaults("p2_nonce_len", gettext("phase 2 nonce length"),
            gettext("bytes"), ddp->rule_p2_nonce_len,
            dp->rule_p2_nonce_len);

        print_defaults("p2_pfs", gettext("phase 2 PFS"),
            " ", ddp->rule_p2_pfs, dp->rule_p2_pfs);

        print_defaults("max_certs", gettext("max certificates"),
            " ", ddp->rule_max_certs, dp->rule_max_certs);

        print_defaults("-", gettext("IKE port number"),
            " ", ddp->rule_ike_port, dp->rule_ike_port);

        print_defaults("-", gettext("NAT-T port number"),
            " ", ddp->rule_natt_port, dp->rule_natt_port);
}

static void
print_categories(int level)
{
        int     mask;

        if (level == 0) {
                (void) printf(gettext("No debug categories enabled.\n"));
                return;
        }

        (void) printf(gettext("Debug categories enabled:"));
        for (mask = 1; mask <= D_HIGHBIT; mask <<= 1) {
                if (level & mask)
                        (void) printf("\n\t%s", dbgstr(mask));
        }
        (void) printf("\n");
}

/*PRINTFLIKE2*/
static void
ikeadm_err_exit(ike_err_t *err, char *fmt, ...)
{
        va_list ap;
        char    bailbuf[BUFSIZ];

        va_start(ap, fmt);
        (void) vsnprintf(bailbuf, BUFSIZ, fmt, ap);
        va_end(ap);
        if ((err != NULL) && (err->ike_err == IKE_ERR_SYS_ERR)) {
                bail_msg("%s: %s", bailbuf, (err->ike_err_unix == 0) ?
                    gettext("<unknown error>") : strerror(err->ike_err_unix));
        } else {
                bail_msg("%s: %s", bailbuf, (err == NULL) ?
                    gettext("<unknown error>") : errstr(err->ike_err));
        }
}

/*PRINTFLIKE2*/
static void
ikeadm_err_msg(ike_err_t *err, char *fmt, ...)
{
        va_list ap;
        char    mbuf[BUFSIZ];

        va_start(ap, fmt);
        (void) vsnprintf(mbuf, BUFSIZ, fmt, ap);
        va_end(ap);
        if ((err != NULL) && (err->ike_err == IKE_ERR_SYS_ERR)) {
                message("%s: %s", mbuf, (err->ike_err_unix == 0) ?
                    gettext("<unknown error>") :
                    ((err->ike_err_unix == EEXIST) ?
                    gettext("Duplicate entry") :
                    strerror(err->ike_err_unix)));
        } else {
                message("%s: %s", mbuf, (err == NULL) ?
                    gettext("<unknown error>") : errstr(err->ike_err));
        }
}


/*
 * Command functions
 */

/*
 * Exploit the fact that ike_dbg_t and ike_priv_t have identical
 * formats in the following two functions.
 */
static void
do_getvar(int cmd)
{
        ike_service_t   req, *rtn;
        ike_dbg_t       *dreq;
        char            *varname;

        switch (cmd) {
        case IKE_SVC_GET_DBG:
                varname = gettext("debug");
                break;
        case IKE_SVC_GET_PRIV:
                varname = gettext("privilege");
                break;
        default:
                bail_msg(gettext("unrecognized get command (%d)"), cmd);
        }

        dreq = &req.svc_dbg;
        dreq->cmd = cmd;
        dreq->dbg_level = 0;

        rtn = ikedoor_call((char *)&req, sizeof (ike_dbg_t), NULL, 0);

        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtn->svc_err,
                    gettext("error getting %s level"), varname);
        }
        dreq = &rtn->svc_dbg;
        (void) printf(gettext("Current %s level is 0x%x"),
            varname, dreq->dbg_level);

        if (cmd == IKE_SVC_GET_DBG) {
                (void) printf("\n");
                print_categories(dreq->dbg_level);
        } else {
                (void) printf(gettext(", %s enabled\n"),
                    privstr(dreq->dbg_level));
        }
}

/*
 * Log into a token and unlock all objects
 * referenced by PKCS#11 hint files.
 */
static void
do_setdel_pin(int cmd, int argc, char **argv)
{
        ike_service_t   req, *rtn;
        ike_pin_t       *preq;
        char            token_label[PKCS11_TOKSIZE];
        char            *token_pin;
        char            prompt[80];

        if (argc < 1)
                Bail(gettext("Must specify PKCS#11 token object."));

        preq = &req.svc_pin;
        preq->cmd = cmd;

        switch (cmd) {
        case IKE_SVC_SET_PIN:
                if (parse_token(argc, argv, token_label) != 0)
                        Bail("Invalid syntax for \"token login\"");
                (void) snprintf(prompt, sizeof (prompt),
                    "Enter PIN for PKCS#11 token \'%s\': ", token_label);
                token_pin =
                    getpassphrase(prompt);
                (void) strlcpy((char *)preq->token_pin, token_pin, MAX_PIN_LEN);
                bzero(token_pin, strlen(token_pin));
                break;
        case IKE_SVC_DEL_PIN:
                if (parse_token(argc, argv, token_label) != 0)
                        Bail("Invalid syntax for \"token logout\"");
                break;
        default:
                bail_msg(gettext("unrecognized token command (%d)"), cmd);
        }

        (void) strlcpy(preq->pkcs11_token, token_label, PKCS11_TOKSIZE);

        rtn = ikedoor_call((char *)&req, sizeof (ike_pin_t), NULL, 0);
        if (cmd == IKE_SVC_SET_PIN)
                bzero(preq->token_pin, sizeof (preq->token_pin));

        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtn->svc_err,
                    gettext("PKCS#11 operation"));
        }
        preq = &rtn->svc_pin;
        message(gettext("PKCS#11 operation successful"));
}

static void
do_setvar(int cmd, int argc, char **argv)
{
        ike_service_t   req, *rtn;
        ike_dbg_t       *dreq;
        door_desc_t     *descp = NULL, desc;
        int             fd, ndesc = 0;
        uint32_t        reqlevel;
        char            *varname;

        if (argc < 1)
                Bail("unspecified level");
        reqlevel = strtoul(argv[0], NULL, 0);

        switch (cmd) {
        case IKE_SVC_SET_DBG:
                if (argc > 2)
                        Bail("Too many arguments to \"set debug\"");
                varname = gettext("debug");
                if (reqlevel == 0) {
                        /* check for a string... */
                        reqlevel = parsedbgopts(argv[0]);
                }
                if (reqlevel == D_INVALID)
                        bail_msg(gettext("Bad debug flag: %s"), argv[0]);
                break;
        case IKE_SVC_SET_PRIV:
                if (argc > 1)
                        Bail("Too many arguments to \"set priv\"");

                varname = gettext("privilege");
                if (reqlevel == 0) {
                        /* check for a string... */
                        reqlevel = privstr2num(argv[0]);
                }
                if (reqlevel > IKE_PRIV_MAXIMUM)
                        bail_msg(gettext("Bad privilege flag: %s"), argv[0]);
                break;
        default:
                bail_msg(gettext("unrecognized set command (%d)"), cmd);
        }

        dreq = &req.svc_dbg;
        dreq->cmd = cmd;
        dreq->dbg_level = reqlevel;

        if ((argc == 2) && (cmd == IKE_SVC_SET_DBG)) {
                fd = open(argv[1], O_RDWR | O_CREAT | O_APPEND,
                    S_IRUSR | S_IWUSR);
                if (fd < 0)
                        Bail("open debug file");
                desc.d_data.d_desc.d_descriptor = fd;
                desc.d_attributes = DOOR_DESCRIPTOR;
                descp = &desc;
                ndesc = 1;
        }

        rtn = ikedoor_call((char *)&req, sizeof (ike_dbg_t), descp, ndesc);

        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtn->svc_err,
                    gettext("error setting %s level"), varname);
        }
        dreq = &rtn->svc_dbg;
        (void) printf(
            gettext("Successfully changed %s level from 0x%x to 0x%x\n"),
            varname, dreq->dbg_level, reqlevel);

        if (cmd == IKE_SVC_SET_DBG) {
                print_categories(reqlevel);
        } else {
                (void) printf(gettext("New privilege level 0x%x enables %s\n"),
                    reqlevel, privstr(reqlevel));
        }
}

static void
do_getstats(int cmd)
{
        ike_service_t   *rtn;
        ike_statreq_t   sreq, *sreqp;
        ike_stats_t     *sp;

        sreq.cmd = cmd;

        rtn = ikedoor_call((char *)&sreq, sizeof (ike_statreq_t), NULL, 0);
        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtn->svc_err, gettext("error getting stats"));
        }

        sreqp = &rtn->svc_stats;
        sp = (ike_stats_t *)(sreqp + 1);
        print_stats(sp, sreqp->stat_len);
}

static void
do_getdefs(int cmd)
{
        ike_service_t   *rtn;
        ike_defreq_t    dreq, *dreqp;
        ike_defaults_t  *dp;

        dreq.cmd = cmd;

        rtn = ikedoor_call((char *)&dreq, sizeof (ike_defreq_t), NULL, 0);
        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtn->svc_err,
                    gettext("error getting defaults"));
        }

        dreqp = &rtn->svc_defaults;
        dp = (ike_defaults_t *)(dreqp + 1);

        /*
         * Before printing each line, make sure the structure we were
         * given is big enough to include the fields needed.
         * Silently bail out of there is a version mismatch.
         */
        if (dreqp->stat_len < ((2 * sizeof (ike_defaults_t))
            + sizeof (ike_defreq_t)) || dreqp->version != DOORVER) {
                return;
        }
        do_print_defaults(dp);
}

static void
do_dump(int cmd)
{
        char            *name;
        ike_service_t   req, *rtn;
        ike_dump_t      *dreq, *dump;

        switch (cmd) {
        case IKE_SVC_DUMP_P1S:
                name = gettext("phase 1 SA info");
                break;
        case IKE_SVC_DUMP_RULES:
                name = gettext("policy rules");
                break;
        case IKE_SVC_DUMP_PS:
                name = gettext("preshared keys");
                break;
        case IKE_SVC_DUMP_CERTCACHE:
                name = gettext("certcache");
                break;
        case IKE_SVC_DUMP_GROUPS:
                name = gettext("groups");
                print_group_header();
                break;
        case IKE_SVC_DUMP_ENCRALGS:
                name = gettext("encralgs");
                print_encralg_header();
                break;
        case IKE_SVC_DUMP_AUTHALGS:
                name = gettext("authalgs");
                print_authalg_header();
                break;
        default:
                bail_msg(gettext("unrecognized dump command (%d)"), cmd);
        }

        dreq = &req.svc_dump;
        dreq->cmd = cmd;
        dreq->dump_len = 0;
        dreq->dump_next = 0;
        do {
                rtn = ikedoor_call((char *)&req, sizeof (ike_dump_t),
                    NULL, 0);
                if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                        if (rtn && (rtn->svc_err.ike_err == IKE_ERR_NO_OBJ)) {
                                /* no entries to print */
                                break;
                        }
                        ikeadm_err_exit(&rtn->svc_err,
                            gettext("error getting %s"), name);
                }
                dump = &rtn->svc_dump;

                switch (cmd) {
                case IKE_SVC_DUMP_P1S:
                        print_p1((ike_p1_sa_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_RULES:
                        print_rule((ike_rule_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_PS:
                        print_ps((ike_ps_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_CERTCACHE:
                        print_certcache((ike_certcache_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_GROUPS:
                        print_group((ike_group_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_ENCRALGS:
                        print_encralg((ike_encralg_t *)(dump + 1));
                        break;
                case IKE_SVC_DUMP_AUTHALGS:
                        print_authalg((ike_authalg_t *)(dump + 1));
                        break;
                }

                dreq->dump_next = dump->dump_next;

                (void) munmap((char *)rtn, dump->dump_len);

        } while (dreq->dump_next);

        (void) printf(gettext("\nCompleted dump of %s\n"), name);
}

static void
do_getdel_doorcall(int cmd, int idlen, int idtype, char *idp, char *name)
{
        int             totallen;
        char            *p;
        ike_service_t   *reqp, *rtnp;
        ike_get_t       *getp;
        boolean_t       getcmd;

        getcmd = ((cmd == IKE_SVC_GET_P1) || (cmd == IKE_SVC_GET_RULE) ||
            (cmd == IKE_SVC_GET_PS));

        /*
         * WARNING: to avoid being redundant, this code takes advantage
         * of the fact that the ike_get_t and ike_del_t structures are
         * identical (only the field names differ, their function and
         * size are the same).  If for some reason those structures
         * change, this code will need to be re-written to accomodate
         * that difference.
         */
        totallen = sizeof (ike_get_t) + idlen;
        if ((reqp = (ike_service_t *)malloc(totallen)) == NULL)
                Bail("malloc(id)");

        getp = &reqp->svc_get;
        getp->cmd = cmd;
        getp->get_len = totallen;
        getp->get_idtype = idtype;
        p = (char *)(getp + 1);

        (void) memcpy(p, idp, idlen);

        rtnp = ikedoor_call((char *)reqp, totallen, NULL, 0);
        if ((rtnp == NULL) || (rtnp->svc_err.cmd == IKE_SVC_ERROR)) {
                if (rtnp && (rtnp->svc_err.ike_err == IKE_ERR_NO_OBJ)) {
                        message(gettext("Could not find requested %s."), name);
                } else {
                        ikeadm_err_msg(&rtnp->svc_err, gettext("error %s %s"),
                            (getcmd) ? gettext("getting") : gettext("deleting"),
                            name);
                }
                free(reqp);
                return;
        }
        getp = &rtnp->svc_get;

        if (getcmd) {
                switch (cmd) {
                case IKE_SVC_GET_P1:
                        print_p1((ike_p1_sa_t *)(getp + 1));
                        break;
                case IKE_SVC_GET_PS:
                        print_ps((ike_ps_t *)(getp + 1));
                        break;
                case IKE_SVC_GET_RULE:
                        print_rule((ike_rule_t *)(getp + 1));
                        break;
                }
        } else {
                message(gettext("Successfully deleted selected %s."), name);
        }

        (void) munmap((char *)rtnp, getp->get_len);
        free(reqp);
}

static void
do_getdel(int cmd, int argc, char **argv)
{
        int             idlen, idtype = 0, i, j;
        int             bytelen1, bytelen2;
        char            *name, *idp, *p, *p1, *p2;
        ike_addr_pr_t   apr;
        ike_cky_pr_t    cpr;
        sadb_ident_t    *sid1p, *sid2p;
        struct hostent  *he1p, *he2p;
        char            label[MAX_LABEL_LEN];

        if ((argc < 1) || (argv[0] == NULL)) {
                Bail("not enough identification info");
        }

        switch (cmd) {
        case IKE_SVC_GET_P1:
        case IKE_SVC_DEL_P1:
                name = gettext("phase 1 SA");
                /*
                 * The first token must either be an address (or hostname)
                 * or a cookie.  We require cookies to be entered as hex
                 * numbers, beginning with 0x; so if our token starts with
                 * that, it's a cookie.
                 */
                if (strncmp(argv[0], "0x", 2) == 0) {
                        if (parse_cky_pr(argc, argv, &cpr) >= 0) {
                                idtype = IKE_ID_CKY_PAIR;
                                idlen = sizeof (ike_cky_pr_t);
                                idp = (char *)&cpr;
                        }
                } else {
                        if (parse_addr_pr(argc, argv, &he1p, &he2p) >= 0) {
                                idtype = IKE_ID_ADDR_PAIR;
                                idlen = sizeof (ike_addr_pr_t);
                        }
                }
                break;

        case IKE_SVC_GET_RULE:
        case IKE_SVC_DEL_RULE:
                name = gettext("policy rule");
                if (parse_label(argc, argv, label) >= 0) {
                        idtype = IKE_ID_LABEL;
                        idlen = MAX_LABEL_LEN;
                        idp = label;
                }
                break;

        case IKE_SVC_GET_PS:
        case IKE_SVC_DEL_PS:
                name = gettext("preshared key");
                /*
                 * The first token must either be an address or an ident
                 * type.  Check for an ident type to determine which it is.
                 */
                if (parse_idtype(argv[0], NULL) >= 0) {
                        if (parse_ident_pr(argc, argv, &sid1p, &sid2p) >= 0) {
                                idtype = IKE_ID_IDENT_PAIR;
                                idlen = SADB_64TO8(sid1p->sadb_ident_len) +
                                    SADB_64TO8(sid2p->sadb_ident_len);
                        }
                } else {
                        if (parse_addr_pr(argc, argv, &he1p, &he2p) >= 0) {
                                idtype = IKE_ID_ADDR_PAIR;
                                idlen = sizeof (ike_addr_pr_t);
                        }
                }
                break;

        default:
                bail_msg(gettext("unrecognized get/del command (%d)"), cmd);
        }

        switch (idtype) {
        case IKE_ID_ADDR_PAIR:
                /*
                 * we might have exploding addrs here; do every possible
                 * combination.
                 */
                i = 0;
                j = 0;
                while ((p1 = he1p->h_addr_list[i++]) != NULL) {
                        headdr2sa(p1, &apr.loc_addr, he1p->h_length);

                        while ((p2 = he2p->h_addr_list[j++]) != NULL) {
                                headdr2sa(p2, &apr.rem_addr, he2p->h_length);
                                do_getdel_doorcall(cmd, idlen, idtype,
                                    (char *)&apr, name);
                        }
                }
                FREE_HE(he1p);
                FREE_HE(he2p);
                break;

        case IKE_ID_IDENT_PAIR:
                bytelen1 = SADB_64TO8(sid1p->sadb_ident_len);
                bytelen2 = SADB_64TO8(sid2p->sadb_ident_len);
                if (idlen != bytelen1 + bytelen2)
                        Bail("ident syntax error");
                idp = p = (char *)malloc(idlen);
                if (p == NULL)
                        Bail("malloc(id)");
                (void) memcpy(p, (char *)sid1p, bytelen1);
                p += bytelen1;
                (void) memcpy(p, (char *)sid2p, bytelen2);
                do_getdel_doorcall(cmd, idlen, idtype, idp, name);
                free(idp);
                free(sid1p);
                free(sid2p);
                break;

        case IKE_ID_CKY_PAIR:
        case IKE_ID_LABEL:
                do_getdel_doorcall(cmd, idlen, idtype, idp, name);
                break;

        case 0:
        default:
                bail_msg(gettext("invalid %s identification\n"), name);
        }
}

/*
 * Copy source into target, inserting an escape character ('\') before
 * any quotes that appear.  Return true on success, false on failure.
 */
static boolean_t
escapequotes(char *target, char *source, int tlen)
{
        int     s, t, len = strlen(source) + 1;

        if (tlen < len)
                return (B_FALSE);

        for (s = 0, t = 0; s < len && t < tlen; s++) {
                if (source[s] == '\"')
                        target[t++] = '\\';
                target[t++] = source[s];
        }

        if ((t == tlen) && (s < len))
                return (B_FALSE);

        return (B_TRUE);
}

/*
 * Return true if the arg following the given keyword should
 * be in quotes (i.e. is a string), false if not.
 */
static boolean_t
quotedfield(char *keywd)
{
        if ((strncmp(keywd, "label", strlen("label") + 1) == 0) ||
            (strncmp(keywd, "local_id", strlen("local_id") + 1) == 0) ||
            (strncmp(keywd, "remote_id", strlen("remote_id") + 1) == 0))
                return (B_TRUE);

        return (B_FALSE);
}

static void
do_new(int cmd, int argc, char **argv)
{
        ike_service_t   *rtn;
        ike_new_t       new, *newp = NULL;
        door_desc_t     desc, *descp = NULL;
        int             i, fd, ndesc = 0, buflen;
        char            *name, tmpfilepath[32];
        FILE            *tmpfile;

        switch (cmd) {
        case IKE_SVC_NEW_PS:
                name = gettext("preshared key");
                break;
        case IKE_SVC_NEW_RULE:
                name = gettext("policy rule");
                break;
        default:
                bail_msg(gettext("unrecognized new command (%d)"), cmd);
        }

        if (argc == 1) {
                /* We've been given a file to read from */
                fd = open(argv[0], O_RDONLY);
                if (fd < 0)
                        Bail("open source file");

                desc.d_data.d_desc.d_descriptor = fd;
                desc.d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE;
                descp = &desc;
                ndesc = 1;

                new.cmd = cmd;
                new.new_len = 0;
                newp = &new;
                buflen = sizeof (ike_new_t);

        } else if ((argc > 1) && (cmd == IKE_SVC_NEW_PS)) {
                /*
                 * This is an alternative to using the tmpfile method
                 * for preshared keys.  It means we're duplicating the
                 * parsing effort that happens in readps.c; but it
                 * does avoid having the key sitting in a file.
                 */
                ike_ps_t        *psp;
                int             pslen;

                /*
                 * must be in interactive mode; don't want keys in
                 * the process args.
                 */
                if (!interactive)
                        Bail("Must be in interactive mode to add key info.");
                if (parse_ps(argc, argv, &psp, &pslen) < 0) {
                        errno = 0;
                        Bail("invalid preshared key definition");
                }
                newp = malloc(sizeof (ike_new_t) + pslen);
                if (newp == NULL)
                        Bail("alloc pskey");
                newp->cmd = cmd;
                newp->new_len = sizeof (ike_new_t) + pslen;
                (void) memcpy((char *)(newp + 1), psp, pslen);
                buflen = newp->new_len;
                /* parse_ps allocated the ike_ps_t buffer; free it now */
                free(psp);

        } else if ((argc > 1) && (cmd == IKE_SVC_NEW_RULE)) {
                /*
                 * We've been given the item in argv.  However, parsing
                 * rules can get more than a little messy, and in.iked
                 * already has a great parser for this stuff!  So don't
                 * fool around with trying to do the parsing here. Just
                 * write it out to a tempfile, and send the fd to in.iked.
                 *
                 * We could conceivably do this for preshared keys,
                 * rather than duplicating the parsing effort; but that
                 * would mean the key would be written out to a file,
                 * which isn't such a good idea.
                 */
                boolean_t       doquotes = B_FALSE;
                int             rtn;

                if ((argv[0][0] != '{') ||
                    (argv[argc - 1][strlen(argv[argc - 1]) - 1] != '}'))
                        bail_msg(gettext("improperly formatted %s"), name);

                /* attempt to use a fairly unpredictable file name... */
                (void) sprintf(tmpfilepath, "/var/run/%x", (int)gethrtime());
                fd = open(tmpfilepath, O_RDWR | O_CREAT | O_EXCL,
                    S_IRUSR | S_IWUSR);
                if (fd < 0)
                        Bail("cannot open tmpfile");

                /* and make it inaccessible asap */
                if (unlink(tmpfilepath) < 0) {
                        (void) close(fd);
                        Bail("tmpfile error");
                }

                tmpfile = fdopen(fd, "w");
                if (tmpfile == NULL) {
                        (void) close(fd);
                        Bail("cannot write to tmpfile");
                }

                for (i = 0; i < argc; i++) {
                        /*
                         * We have to do some gyrations with our string here,
                         * to properly handle quotes.  There are two issues:
                         * - some of the fields of a rule may have embedded
                         *   whitespace, and thus must be quoted on the cmd
                         *   line.  The shell removes the quotes, and gives
                         *   us a single argv string; but we need to put the
                         *   quotes back in when we write the string out to
                         *   file.  The doquotes boolean is set when we
                         *   process a keyword which will be followed by a
                         *   string value (so the NEXT argv element will be
                         *   quoted).
                         * - there might be a quote character in a field,
                         *   that was escaped on the cmdline.  The shell
                         *   removes the escape char, and leaves the quote
                         *   in the string it gives us.  We need to put the
                         *   escape char back in before writing to file.
                         */
                        char    field[MAXLINESIZE];
                        if (!escapequotes(field, argv[i], MAXLINESIZE))
                                Bail("write to tmpfile failed (arg too big)");
                        if (doquotes) {
                                rtn = fprintf(tmpfile, "\"%s\"\n", field);
                                doquotes = B_FALSE;
                        } else {
                                rtn = fprintf(tmpfile, "%s\n", field);
                        }
                        if (rtn < 0)
                                Bail("write to tmpfile failed");
                        /*
                         * check if this is a keyword identifying
                         * a field that needs to be quoted.
                         */
                        doquotes = quotedfield(argv[i]);
                }
                if (fflush(tmpfile) == EOF)
                        Bail("write to tmpfile failed");
                /* rewind so that the daemon will get the beginning */
                rewind(tmpfile);

                desc.d_data.d_desc.d_descriptor = fd;
                desc.d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE;
                descp = &desc;
                ndesc = 1;

                new.cmd = cmd;
                new.new_len = 0;
                newp = &new;
                buflen = sizeof (ike_new_t);

        } else {
                /* not enough information! */
                bail_msg(gettext("missing %s description or file name"), name);
        }

        rtn = ikedoor_call((char *)newp, buflen, descp, ndesc);

        if ((rtn == NULL) || (rtn->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_msg(&rtn->svc_err,
                    gettext("error creating new %s"), name);
        } else {
                message(gettext("Successfully created new %s."), name);
        }
}

static void
do_flush(int cmd)
{
        ike_service_t   *rtnp;
        ike_flush_t     flush;

        if (cmd != IKE_SVC_FLUSH_P1S && cmd != IKE_SVC_FLUSH_CERTCACHE) {
                bail_msg(gettext("unrecognized flush command (%d)."), cmd);
        }

        flush.cmd = cmd;

        rtnp = ikedoor_call((char *)&flush, sizeof (ike_flush_t), NULL, 0);
        if ((rtnp == NULL) || (rtnp->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtnp->svc_err, gettext("error doing flush"));
        }
        if (cmd == IKE_SVC_FLUSH_P1S)
                message(gettext("Successfully flushed P1 SAs."));
        else
                message(gettext("Successfully flushed cert cache."));
}

static void
do_rw(int cmd, int argc, char **argv)
{
        ike_service_t   *rtnp;
        ike_rw_t        rw;
        door_desc_t     desc, *descp = NULL;
        int             oflag, omode, fd, ndesc = 0;
        char            *op, *obj = NULL;
        boolean_t       writing = B_FALSE;

        switch (cmd) {
        case IKE_SVC_READ_PS:
                obj = gettext("preshared key");
                /* FALLTHRU */
        case IKE_SVC_READ_RULES:
                if (obj == NULL)
                        obj = gettext("policy rule");
                op = gettext("read");
                oflag = O_RDONLY;
                omode = 0;
                break;

        case IKE_SVC_WRITE_PS:
                obj = gettext("preshared key");
                /* FALLTHRU */
        case IKE_SVC_WRITE_RULES:
                if (obj == NULL)
                        obj = gettext("policy rule");
                op = gettext("write");
                oflag = O_RDWR | O_CREAT | O_EXCL;
                omode = S_IRUSR | S_IWUSR;

                /* for write commands, dest location must be specified */
                if (argc < 1) {
                        bail_msg(gettext("destination location required "
                            "to write %ss"), obj);
                }
                writing = B_TRUE;
                break;

        default:
                bail_msg(gettext("unrecognized read/write command (%d)."), cmd);
        }

        rw.cmd = cmd;

        if (argc >= 1) {
                rw.rw_loc = IKE_RW_LOC_USER_SPEC;
                fd = open(argv[0], oflag, omode);
                if (fd < 0)
                        Bail("open user-specified file");

                desc.d_data.d_desc.d_descriptor = fd;
                desc.d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE;
                descp = &desc;
                ndesc = 1;
        } else {
                rw.rw_loc = IKE_RW_LOC_DEFAULT;
        }

        rtnp = ikedoor_call((char *)&rw, sizeof (ike_rw_t), descp, ndesc);
        if ((rtnp == NULL) || (rtnp->svc_err.cmd == IKE_SVC_ERROR)) {
                /*
                 * Need to remove the target file in the
                 * case of a failed write command.
                 */
                if (writing) {
                        /*
                         * argv[0] must be valid if we're writing; we
                         * exit before setting this boolean if not.
                         */
                        (void) unlink(argv[0]);
                        (void) close(fd);

                        if ((rtnp != NULL) &&
                            (rtnp->svc_err.ike_err == IKE_ERR_NO_OBJ)) {
                                message(gettext("No %s information to write."),
                                    obj);
                                return;
                        }
                }
                ikeadm_err_exit(&rtnp->svc_err, gettext("error doing %s"), op);
        }
        message(gettext("Completed %s of %s configuration information."),
            op, obj);
}

static void
do_rbdump()
{
        ike_cmd_t       req;
        ike_service_t   *rtnp;

        req.cmd = IKE_SVC_DBG_RBDUMP;

        rtnp = ikedoor_call((char *)&req, sizeof (ike_cmd_t), NULL, 0);
        if ((rtnp == NULL) || (rtnp->svc_err.cmd == IKE_SVC_ERROR)) {
                ikeadm_err_exit(&rtnp->svc_err, gettext("error doing flush"));
        }
        message(gettext("Successfully dumped rulebase; check iked dbg"));
}

#define REQ_ARG_CNT     1

/*ARGSUSED*/
static void
parseit(int argc, char **argv, char *notused, boolean_t notused_either)
{
        int     cmd, cmd_obj_args = 1;
        char    *cmdstr, *objstr;

        if (interactive) {
                if (argc == 0)
                        return;
        }

        if (argc < REQ_ARG_CNT) {
                usage();
        }

        cmdstr = argv[0];
        if (argc > REQ_ARG_CNT) {
                cmd_obj_args++;
                objstr = argv[1];
        } else {
                objstr = NULL;
        }
        cmd = parsecmd(cmdstr, objstr);

        /* skip over args specifying command/object */
        argc -= cmd_obj_args;
        argv += cmd_obj_args;

        switch (cmd) {
        case IKE_SVC_GET_DEFS:
                if (argc != 0) {
                        print_get_help();
                        break;
                }
                do_getdefs(cmd);
                break;
        case IKE_SVC_GET_DBG:
        case IKE_SVC_GET_PRIV:
                if (argc != 0) {
                        print_get_help();
                        break;
                }
                do_getvar(cmd);
                break;
        case IKE_SVC_GET_STATS:
                if (argc != 0) {
                        print_get_help();
                        break;
                }
                do_getstats(cmd);
                break;
        case IKE_SVC_SET_DBG:
        case IKE_SVC_SET_PRIV:
                do_setvar(cmd, argc, argv);
                break;
        case IKE_SVC_SET_PIN:
        case IKE_SVC_DEL_PIN:
                do_setdel_pin(cmd, argc, argv);
                break;
        case IKE_SVC_DUMP_P1S:
        case IKE_SVC_DUMP_RULES:
        case IKE_SVC_DUMP_GROUPS:
        case IKE_SVC_DUMP_ENCRALGS:
        case IKE_SVC_DUMP_AUTHALGS:
        case IKE_SVC_DUMP_PS:
        case IKE_SVC_DUMP_CERTCACHE:
                if (argc != 0) {
                        print_dump_help();
                        break;
                }
                do_dump(cmd);
                break;
        case IKE_SVC_GET_P1:
        case IKE_SVC_GET_RULE:
        case IKE_SVC_GET_PS:
        case IKE_SVC_DEL_P1:
        case IKE_SVC_DEL_RULE:
        case IKE_SVC_DEL_PS:
                do_getdel(cmd, argc, argv);
                break;
        case IKE_SVC_NEW_RULE:
        case IKE_SVC_NEW_PS:
                do_new(cmd, argc, argv);
                break;
        case IKE_SVC_FLUSH_P1S:
        case IKE_SVC_FLUSH_CERTCACHE:
                if (argc != 0) {
                        print_flush_help();
                        break;
                }
                do_flush(cmd);
                break;
        case IKE_SVC_READ_RULES:
        case IKE_SVC_READ_PS:
        case IKE_SVC_WRITE_RULES:
        case IKE_SVC_WRITE_PS:
                do_rw(cmd, argc, argv);
                break;
        case IKEADM_HELP_GENERAL:
                print_help();
                break;
        case IKEADM_HELP_GET:
                print_get_help();
                break;
        case IKEADM_HELP_SET:
                print_set_help();
                break;
        case IKEADM_HELP_ADD:
                print_add_help();
                break;
        case IKEADM_HELP_DEL:
                print_del_help();
                break;
        case IKEADM_HELP_DUMP:
                print_dump_help();
                break;
        case IKEADM_HELP_FLUSH:
                print_flush_help();
                break;
        case IKEADM_HELP_READ:
                print_read_help();
                break;
        case IKEADM_HELP_WRITE:
                print_write_help();
                break;
        case IKEADM_HELP_TOKEN:
                print_token_help();
                break;
        case IKEADM_HELP_HELP:
                print_help_help();
                break;
        case IKEADM_EXIT:
                if (interactive)
                        exit(0);
                break;
        case IKE_SVC_DBG_RBDUMP:
                do_rbdump();
                break;
        case IKE_SVC_ERROR:
                usage();
        default:
                exit(0);
        }
}

int
main(int argc, char **argv)
{
        int     ch;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        while ((ch = getopt(argc, argv, "hpn")) != EOF) {
                switch (ch) {
                case 'h':
                        print_help();
                        return (0);
                case 'p':
                        pflag = B_TRUE;
                        break;
                case 'n':
                        nflag = B_TRUE;
                        break;
                default:
                        usage();
                }
        }
        argc -= optind;
        argv += optind;

        if (open_door() < 0) {
                (void) fprintf(stderr,
                    gettext("Unable to communicate with in.iked\n"));
                Bail("open_door failed");
        }

        if (*argv == NULL) {
                /* no cmd-line args, do interactive mode */
                do_interactive(stdin, NULL, "ikeadm> ", NULL, parseit,
                    no_match);
        }

        parseit(argc, argv, NULL, B_FALSE);

        return (0);
}