view usr/src/cmd/cmd-inet/usr.sbin/ipsecutils/ikeadm.c @ 10934:e209937a4f19

PSARC/2008/252 Labeled IPsec phase 1 6886771 Labeled IPsec phase 1 6808727 Alignment error panic in tsol_can_accept_raw() 6894979 nightly -0 + -p builds then destroys SUNW0on
author Bill Sommerfeld <sommerfeld@sun.com>
date Mon, 02 Nov 2009 15:39:20 -0800
parents c47254a96e5d
children 5049dc761777
line wrap: on
line source

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#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)
{
	if (interactive) {
		longjmp(env, 1);
	} else {
		exit(s);
	}
}

static void
usage()
{
	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\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("\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},
				{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"},
	{NULL,	0}
};

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"},
	{NULL,	0}
};

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"},
	{NULL,	0}
};

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;
	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;

	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;
	}

	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) {
		/*
		 * 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) {
		/*
		 * 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);

	*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)"));
	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)
{
	(void) printf(gettext("%s Lifetime limits:\n"), prefix);
	(void) printf(gettext("%s %u seconds; %u kbytes protected; "),
	    prefix, xfp->p1xf_max_secs, xfp->p1xf_max_kbytes);
	(void) printf(gettext("%u keymat provided.\n"), xfp->p1xf_max_keyuses);
}

#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];

	(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 protected; %u keymat provided.\n"),
	    prefix, sp->p1stat_kbytes, 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];

	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 can be protected.\n"),
		    prefix, xfp->p1xf_max_kbytes - sp->p1stat_kbytes);

	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, at %s\n"),
			    prefix, remain, 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)
{
	(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, 0, 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);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_SKEYID:
			(void) printf(gettext("%s SKEYID (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_SKEYID_D:
			(void) printf(gettext("%s SKEYID_d (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_SKEYID_A:
			(void) printf(gettext("%s SKEYID_a (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_SKEYID_E:
			(void) printf(gettext("%s SKEYID_e (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_ENCR:
			(void) printf(gettext("%s Encryption key (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		case IKE_KEY_IV:
			(void) printf(
			    gettext("%s Initialization vector (%d bytes): "),
			    prefix, len);
			(void) dump_key((uint8_t *)(p + 1), SADB_8TO1(len), 0,
			    stdout, B_FALSE);
			break;
		default:
			(void) printf(gettext("%s Unidentified key info %p %d"),
			    prefix, p, p1klen);
		}
		(void) printf("\n");
		assert(IS_P2ALIGNED(p1klen, 8));
		curp += (p1klen >> 2);
	}
}

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);
	print_addr("REMIP:", &p1->p1sa_ipaddrs.rem_addr, rstat);

	/*
	 * 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 != NULL) ? c->subject : gettext("Name unavailable"));
	(void) printf(gettext("\t Issuer Name: <%s>\n"),
	    (c->issuer != NULL) ? c->issuer : gettext("Name unavailable"));
	if ((int)c->class == -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);
	}

	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);
	}
}

#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;

	(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, p2_softlife=%u seconds\n"),
	    rp->rule_p2_lifetime_secs, rp->rule_p2_softlife_secs);
	(void) printf(
	    gettext("GLOBL: p2_idletime=%u seconds\n"),
	    rp->rule_p2_idletime_secs);
	(void) printf(
	    gettext("GLOBL: p2_lifetime_kb=%u seconds,"
	    " p2_softlife_kb=%u seconds\n"),
	    rp->rule_p2_lifetime_kb, rp->rule_p2_softlife_kb);

	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);
}

static void
print_defaults(char *label, char *description, char *unit, boolean_t kbytes,
    uint_t current, uint_t def)
{
	(void) printf("%-18s%-10s%14u%s%-10s%-26s\n", label,
	    (current != def) ? gettext("config") : gettext("default"),
	    (current != def) ? current : def, (kbytes) ? "K " : "  ",
	    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.
 */
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\n"));

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

	print_defaults("p1_lifetime_secs", gettext("phase 1 lifetime"),
	    gettext("seconds"), B_FALSE, ddp->rule_p1_lifetime_secs,
	    dp->rule_p1_lifetime_secs);

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

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

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

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

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

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

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

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

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

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

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

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

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

	print_defaults("p2_nonce_len", gettext("phase 2 nonce length"),
	    gettext("bytes"), B_FALSE, ddp->rule_p2_nonce_len,
	    dp->rule_p2_nonce_len);

	print_defaults("-", gettext("default phase 2 lifetime"),
	    gettext("seconds"), B_FALSE, ddp->rule_p2_def_minlife,
	    dp->rule_p2_def_minlife);

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

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

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

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

	print_defaults("-", gettext("NAT-T port number"),
	    " ", B_FALSE, 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;
	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;
		}

		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_PS:
	case IKE_SVC_DUMP_CERTCACHE:
		if (argc != NULL) {
			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)
{
	char	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);
}