/*	$NetBSD: racoonctl.c,v 1.7.6.2 2009/04/20 13:32:57 tteras Exp $	*/

/*	Id: racoonctl.c,v 1.11 2006/04/06 17:06:25 manubsd Exp */

/*
 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/pfkeyv2.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include <netdb.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <err.h>
#include <sys/ioctl.h> 
#include <resolv.h>

#include "var.h"
#include "vmbuf.h"
#include "misc.h"
#include "gcmalloc.h"

#include "racoonctl.h"
#include "admin.h"
#include "schedule.h"
#include "handler.h"
#include "sockmisc.h"
#include "vmbuf.h"
#include "plog.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "isakmp_xauth.h"
#include "isakmp_cfg.h"
#include "isakmp_unity.h"
#include "ipsec_doi.h"
#include "evt.h"

char *adminsock_path = ADMINSOCK_PATH;

static void usage __P((void));
static vchar_t *get_combuf __P((int, char **));
static int handle_recv __P((vchar_t *));
static vchar_t *f_reload __P((int, char **));
static vchar_t *f_getsched __P((int, char **));
static vchar_t *f_getsa __P((int, char **));
static vchar_t *f_flushsa __P((int, char **));
static vchar_t *f_deletesa __P((int, char **));
static vchar_t *f_exchangesa __P((int, char **));
static vchar_t *f_vpnc __P((int, char **));
static vchar_t *f_vpnd __P((int, char **));
static vchar_t *f_getevt __P((int, char **));
#ifdef ENABLE_HYBRID
static vchar_t *f_logoutusr __P((int, char **));
#endif

struct cmd_tag {
	vchar_t *(*func) __P((int, char **));
	int cmd;
	char *str;
} cmdtab[] = {
	{ f_reload,	ADMIN_RELOAD_CONF,	"reload-config" },
	{ f_reload,	ADMIN_RELOAD_CONF,	"rc" },
	{ f_getsched,	ADMIN_SHOW_SCHED,	"show-schedule" },
	{ f_getsched,	ADMIN_SHOW_SCHED,	"sc" },
	{ f_getsa,	ADMIN_SHOW_SA,		"show-sa" },
	{ f_getsa,	ADMIN_SHOW_SA,		"ss" },
	{ f_flushsa,	ADMIN_FLUSH_SA,		"flush-sa" },
	{ f_flushsa,	ADMIN_FLUSH_SA,		"fs" },
	{ f_deletesa,	ADMIN_DELETE_SA,	"delete-sa" },
	{ f_deletesa,	ADMIN_DELETE_SA,	"ds" },
	{ f_exchangesa,	ADMIN_ESTABLISH_SA,	"establish-sa" },
	{ f_exchangesa,	ADMIN_ESTABLISH_SA,	"es" },
	{ f_vpnc,	ADMIN_ESTABLISH_SA,	"vpn-connect" },
	{ f_vpnc,	ADMIN_ESTABLISH_SA,	"vc" },
	{ f_vpnd,	ADMIN_DELETE_ALL_SA_DST,"vpn-disconnect" },
	{ f_vpnd,	ADMIN_DELETE_ALL_SA_DST,"vd" },
	{ f_getevt,	ADMIN_SHOW_EVT,		"show-event" },
	{ f_getevt,	ADMIN_SHOW_EVT,		"se" },
#ifdef ENABLE_HYBRID
	{ f_logoutusr,	ADMIN_LOGOUT_USER,	"logout-user" },
	{ f_logoutusr,	ADMIN_LOGOUT_USER,	"lu" },
#endif
	{ NULL, 0, NULL },
};

struct evtmsg {
	int type;
	char *msg;
	enum { UNSPEC, ERROR, INFO } level;
} evtmsg[] = {
	{ EVTT_PHASE1_UP, "Phase 1 established", INFO },
	{ EVTT_PHASE1_DOWN, "Phase 1 deleted", INFO },
	{ EVTT_XAUTH_SUCCESS, "Xauth exchange passed", INFO },
	{ EVTT_ISAKMP_CFG_DONE, "ISAKMP mode config done", INFO },
	{ EVTT_PHASE2_UP, "Phase 2 established", INFO },
	{ EVTT_PHASE2_DOWN, "Phase 2 deleted", INFO },
	{ EVTT_DPD_TIMEOUT, "Peer not reachable anymore", ERROR },
	{ EVTT_PEER_NO_RESPONSE, "Peer not responding", ERROR },
	{ EVTT_PEER_DELETE, "Peer terminated security association", ERROR },
	{ EVTT_RACOON_QUIT, "Raccon terminated", ERROR },
	{ EVTT_OVERFLOW, "Event queue overflow", ERROR },
	{ EVTT_XAUTH_FAILED, "Xauth exchange failed", ERROR },
	{ EVTT_PEERPH1AUTH_FAILED, "Peer failed phase 1 authentication "
	    "(certificate problem?)", ERROR },
	{ EVTT_PEERPH1_NOPROP, "Peer failed phase 1 initiation "
	    "(proposal problem?)", ERROR },
	{ 0, NULL, UNSPEC },
	{ EVTT_NO_ISAKMP_CFG, "No need for ISAKMP mode config ", INFO },
};

static int get_proto __P((char *));
static vchar_t *get_index __P((int, char **));
static int get_family __P((char *));
static vchar_t *get_comindexes __P((int, int, char **));
static int get_comindex __P((char *, char **, char **, char **));
static int get_ulproto __P((char *));

struct proto_tag {
	int proto;
	char *str;
} prototab[] = {
	{ ADMIN_PROTO_ISAKMP,	"isakmp" },
	{ ADMIN_PROTO_IPSEC,	"ipsec" },
	{ ADMIN_PROTO_AH,	"ah" },
	{ ADMIN_PROTO_ESP,	"esp" },
	{ ADMIN_PROTO_INTERNAL,	"internal" },
	{ 0, NULL },
};

struct ulproto_tag {
	int ul_proto;
	char *str;
} ulprototab[] = {
	{ 0,		"any" },
	{ IPPROTO_ICMP,	"icmp" },
	{ IPPROTO_TCP,	"tcp" },
	{ IPPROTO_UDP,	"udp" },
	{ 0, NULL },
};

int so;

static char _addr1_[NI_MAXHOST], _addr2_[NI_MAXHOST];

char *pname;
int long_format = 0;

#define EVTF_NONE		0x0000	/* Ignore any events */
#define EVTF_LOOP		0x0001	/* Loop awaiting for new events */
#define EVTF_CFG_STOP		0x0002	/* Stop after ISAKMP mode config */
#define EVTF_CFG		0x0004	/* Print ISAKMP mode config info */
#define EVTF_ALL		0x0008	/* Print any events */
#define EVTF_PURGE		0x0010	/* Print all available events */
#define EVTF_PH1DOWN_STOP	0x0020	/* Stop when phase 1 SA gets down */
#define EVTF_PH1DOWN		0x0040	/* Print that phase 1 SA got down */
#define EVTF_ERR		0x0080	/* Print any error */
#define EVTF_ERR_STOP		0x0100	/* Stop on any error */

int evt_filter = EVTF_NONE;
time_t evt_start;

void dump_isakmp_sa __P((char *, int));
void dump_internal __P((char *, int));
char *pindex_isakmp __P((isakmp_index *));
void print_schedule __P((caddr_t, int));
void print_evt __P((caddr_t, int));
void print_cfg __P((caddr_t, int));
void print_err __P((caddr_t, int));
void print_ph1down __P((caddr_t, int));
void print_ph1up __P((caddr_t, int));
int evt_poll __P((void));
char * fixed_addr __P((char *, char *, int));

static void
usage()
{
	printf(
"Usage:\n"
"  %s reload-config\n"
"  %s [-l [-l]] show-sa [protocol]\n"
"  %s flush-sa [protocol]\n"
"  %s delete-sa <saopts>\n"
"  %s establish-sa [-u identity] <saopts>\n"
"  %s vpn-connect [-u identity] vpn_gateway\n"
"  %s vpn-disconnect vpn_gateway\n"
"\n"
"    <protocol>: \"isakmp\", \"esp\" or \"ah\".\n"
"        In the case of \"show-sa\" or \"flush-sa\", you can use \"ipsec\".\n"
"\n"
"    <saopts>: \"isakmp\" <family> <src> <dst>\n"
"            : {\"esp\",\"ah\"} <family> <src/prefixlen/port> <dst/prefixlen/port>\n"
"                              <ul_proto>\n"
"    <family>: \"inet\" or \"inet6\"\n"
"    <ul_proto>: \"icmp\", \"tcp\", \"udp\" or \"any\"\n",
	pname, pname, pname, pname, pname, pname, pname);
}

/*
 * Check for proper racoonctl interface
 */
#if ((RACOONCTL_INTERFACE_MAJOR != 1) || (RACOONCTL_INTERFACE < 20041230))
#error	"Incompatible racoonctl interface"
#endif

int
main(ac, av)
	int ac;
	char **av;
{
	vchar_t *combuf;
	int c;

	pname = *av;

	/*
	 * Check for proper racoonctl interface
	 */
	if ((racoonctl_interface_major != RACOONCTL_INTERFACE_MAJOR) ||
	    (racoonctl_interface < RACOONCTL_INTERFACE))
		errx(1, "Incompatible racoonctl interface");

#ifdef __linux__
	/*
	 * Disable GNU extensions that will prevent racoonct vc -u login
	 * from working (GNU getopt(3) does not like options after vc)
	 */
	setenv("POSIXLY_CORRECT", "1", 0);
#endif
	while ((c = getopt(ac, av, "lds:")) != -1) {
		switch(c) {
		case 'l':
			long_format++;
			break;

		case 'd':
			loglevel++;
			break;

		case 's':
			adminsock_path = optarg;
			break;

		default:
			usage();
			exit(0);
		}
	}

	ac -= optind;
	av += optind;

	combuf = get_combuf(ac, av);
	if (!combuf)
		err(1, "kmpstat");

	if (loglevel)
		racoon_hexdump(combuf, ((struct admin_com *)combuf)->ac_len);

	com_init();

	if (com_send(combuf) != 0)
		goto bad;

	vfree(combuf);

	if (com_recv(&combuf) != 0)
		goto bad;
	if (handle_recv(combuf) != 0)
		goto bad;

	vfree(combuf);

	if (evt_filter != EVTF_NONE)
		if (evt_poll() != 0)
			goto bad;	
	
	exit(0);

    bad:
	exit(1);
}

int
evt_poll(void) {
	struct timeval tv;
	vchar_t *recvbuf;
	vchar_t *sendbuf;

	if ((sendbuf = f_getevt(0, NULL)) == NULL)
		errx(1, "Cannot make combuf");


	while (evt_filter & (EVTF_LOOP|EVTF_PURGE)) {
		/* handle_recv closes the socket time, so open it each time */
		com_init();

		if (com_send(sendbuf) != 0)
			errx(1, "Cannot send combuf");

		if (com_recv(&recvbuf) == 0) {
			handle_recv(recvbuf);
			vfree(recvbuf);
		}

		tv.tv_sec = 0;
		tv.tv_usec = 10;
		(void)select(0, NULL, NULL, NULL, &tv);
	}

	vfree(sendbuf);
	return 0;
}

/* %%% */
/*
 * return command buffer.
 */
static vchar_t *
get_combuf(ac, av)
	int ac;
	char **av;
{
	struct cmd_tag *cp;

	if (ac == 0) {
		usage();
		exit(0);
	}

	/* checking the string of command. */
	for (cp = &cmdtab[0]; cp->str; cp++) {
		if (strcmp(*av, cp->str) == 0) {
			break;
		}
	}
	if (!cp->str) {
		printf("Invalid command [%s]\n", *av);
		errno = EINVAL;
		return NULL;
	}

	ac--;
	av++;
	return (cp->func)(ac, av);
}

static vchar_t *
f_reload(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;

	buf = vmalloc(sizeof(*head));
	if (buf == NULL)
		errx(1, "not enough core");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_RELOAD_CONF;
	head->ac_errno = 0;
	head->ac_proto = 0;

	return buf;
}

static vchar_t *
f_getevt(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;

	/*
	 * There are 3 ways of getting here
	 * 1) racoonctl vc => evt_filter = (EVTF_LOOP|EVTF_CFG| ... )
	 * 2) racoonctl es => evt_filter = EVTF_NONE
	 * 3) racoonctl es -l => evt_filter = EVTF_LOOP
	 * Catch the second case: show-event is here to purge all
	 */
	if (evt_filter == EVTF_NONE)
		evt_filter = (EVTF_ALL|EVTF_PURGE);

	if ((ac >= 1) && (strcmp(av[0], "-l") == 0))
		evt_filter |= EVTF_LOOP;

	if (ac >= 2)
		errx(1, "too many arguments");

	buf = vmalloc(sizeof(*head));
	if (buf == NULL)
		errx(1, "not enough core");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_SHOW_EVT;
	head->ac_errno = 0;
	head->ac_proto = 0;

	return buf;
}

static vchar_t *
f_getsched(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;

	buf = vmalloc(sizeof(*head));
	if (buf == NULL)
		errx(1, "not enough core");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_SHOW_SCHED;
	head->ac_errno = 0;
	head->ac_proto = 0;

	return buf;
}

static vchar_t *
f_getsa(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;
	int proto;

	/* need protocol */
	if (ac != 1)
		errx(1, "insufficient arguments");
	proto = get_proto(*av);
	if (proto == -1)
		errx(1, "unknown protocol %s", *av);

	buf = vmalloc(sizeof(*head));
	if (buf == NULL)
		errx(1, "not enough core");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_SHOW_SA;
	head->ac_errno = 0;
	head->ac_proto = proto;

	return buf;
}

static vchar_t *
f_flushsa(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;
	int proto;

	/* need protocol */
	if (ac != 1)
		errx(1, "insufficient arguments");
	proto = get_proto(*av);
	if (proto == -1)
		errx(1, "unknown protocol %s", *av);

	buf = vmalloc(sizeof(*head));
	if (buf == NULL)
		errx(1, "not enough core");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_FLUSH_SA;
	head->ac_errno = 0;
	head->ac_proto = proto;

	return buf;
}

static vchar_t *
f_deletesa(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf, *index;
	struct admin_com *head;
	int proto;

	/* need protocol */
	if (ac < 1)
		errx(1, "insufficient arguments");
	proto = get_proto(*av);
	if (proto == -1)
		errx(1, "unknown protocol %s", *av);

	/* get index(es) */
	av++;
	ac--;
	switch (proto) {
	case ADMIN_PROTO_ISAKMP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	case ADMIN_PROTO_AH:
	case ADMIN_PROTO_ESP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	default:
		errno = EPROTONOSUPPORT;
		return NULL;
	}

	buf = vmalloc(sizeof(*head) + index->l);
	if (buf == NULL)
		goto out;

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l + index->l;
	head->ac_cmd = ADMIN_DELETE_SA;
	head->ac_errno = 0;
	head->ac_proto = proto;

	memcpy(buf->v+sizeof(*head), index->v, index->l);

out:
	if (index != NULL)
		vfree(index);

	return buf;
}

static vchar_t *
f_deleteallsadst(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf, *index;
	struct admin_com *head;
	int proto;

	/* need protocol */
	if (ac < 1)
		errx(1, "insufficient arguments");
	proto = get_proto(*av);
	if (proto == -1)
		errx(1, "unknown protocol %s", *av);

	/* get index(es) */
	av++;
	ac--;
	switch (proto) {
	case ADMIN_PROTO_ISAKMP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	case ADMIN_PROTO_AH:
	case ADMIN_PROTO_ESP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	default:
		errno = EPROTONOSUPPORT;
		return NULL;
	}

	buf = vmalloc(sizeof(*head) + index->l);
	if (buf == NULL)
		goto out;

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l + index->l;
	head->ac_cmd = ADMIN_DELETE_ALL_SA_DST;
	head->ac_errno = 0;
	head->ac_proto = proto;

	memcpy(buf->v+sizeof(*head), index->v, index->l);

out:
	if (index != NULL)
		vfree(index);

	return buf;
}

static vchar_t *
f_exchangesa(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf, *index;
	struct admin_com *head;
	int proto;
	int cmd = ADMIN_ESTABLISH_SA;
	size_t com_len = 0;
	char *id = NULL;
	char *key = NULL;
	struct admin_com_psk *acp;

	if (ac < 1)
		errx(1, "insufficient arguments");

	/* Optional -u identity */
	if (strcmp(av[0], "-u") == 0) {
		if (ac < 2)
			errx(1, "-u require an argument");

		id = av[1];
		if ((key = getpass("Password: ")) == NULL)
			errx(1, "getpass() failed: %s", strerror(errno));
		
		com_len += sizeof(*acp) + strlen(id) + 1 + strlen(key) + 1;
		cmd = ADMIN_ESTABLISH_SA_PSK;

		av += 2;
		ac -= 2;
	}

	/* need protocol */
	if (ac < 1)
		errx(1, "insufficient arguments");
	if ((proto = get_proto(*av)) == -1)
		errx(1, "unknown protocol %s", *av);

	/* get index(es) */
	av++;
	ac--;
	switch (proto) {
	case ADMIN_PROTO_ISAKMP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	case ADMIN_PROTO_AH:
	case ADMIN_PROTO_ESP:
		index = get_index(ac, av);
		if (index == NULL)
			return NULL;
		break;
	default:
		errno = EPROTONOSUPPORT;
		return NULL;
	}

	com_len += sizeof(*head) + index->l;
	if ((buf = vmalloc(com_len)) == NULL)
		errx(1, "Cannot allocate buffer");

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = cmd;
	head->ac_errno = 0;
	head->ac_proto = proto;

	memcpy(buf->v+sizeof(*head), index->v, index->l);

	if (id && key) {
		char *data;
		acp = (struct admin_com_psk *)
		    (buf->v + sizeof(*head) + index->l);

		acp->id_type = IDTYPE_USERFQDN;
		acp->id_len = strlen(id) + 1;
		acp->key_len = strlen(key) + 1;

		data = (char *)(acp + 1);
		strcpy(data, id);

		data = (char *)(data + acp->id_len);
		strcpy(data, key);
	}

	vfree(index);

	return buf;
}

static vchar_t *
f_vpnc(ac, av)
	int ac;
	char **av;
{
	char *nav[] = {NULL, NULL, NULL, NULL, NULL, NULL};
	int nac = 0;
	char *isakmp = "isakmp";
	char *inet = "inet";
	char *srcaddr;
	struct addrinfo hints, *res;
	struct sockaddr *src;
	char *idx;

	if (ac < 1)
		errx(1, "insufficient arguments");

	evt_filter = (EVTF_LOOP|EVTF_CFG|EVTF_CFG_STOP|EVTF_ERR|EVTF_ERR_STOP);
	time(&evt_start);
	
	/* Optional -u identity */
	if (strcmp(av[0], "-u") == 0) {
		if (ac < 2)
			errx(1, "-u require an argument");

		nav[nac++] = av[0];
		nav[nac++] = av[1];

		ac -= 2;
		av += 2;
	}

	if (ac < 1)
		errx(1, "VPN gateway required");	
	if (ac > 1)
		warnx("Extra arguments");

	/*
	 * Find the source address
	 */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	if (getaddrinfo(av[0], "4500", &hints, &res) != 0)
		errx(1, "Cannot resolve destination address");

	if ((src = getlocaladdr(res->ai_addr)) == NULL)
		errx(1, "cannot find source address");

	if ((srcaddr = saddr2str(src)) == NULL)
		errx(1, "cannot read source address");

	/* We get "ip[port]" strip the port */
	if ((idx = index(srcaddr, '[')) == NULL) 
		errx(1, "unexpected source address format");
	*idx = '\0';

	nav[nac++] = isakmp;
	nav[nac++] = inet;
	nav[nac++] = srcaddr;
	nav[nac++] = av[0];

	return f_exchangesa(nac, nav);
}

static vchar_t *
f_vpnd(ac, av)
	int ac;
	char **av;
{
	char *nav[] = {NULL, NULL, NULL, NULL};
	int nac = 0;
	char *isakmp = "isakmp";
	char *inet = "inet";
	char *anyaddr = "0.0.0.0";
	char *idx;

	if (ac < 1)
		errx(1, "VPN gateway required");	
	if (ac > 1)
		warnx("Extra arguments");

	evt_filter = 
	    (EVTF_PH1DOWN|EVTF_PH1DOWN_STOP|EVTF_LOOP|EVTF_ERR|EVTF_ERR_STOP);

	nav[nac++] = isakmp;
	nav[nac++] = inet;
	nav[nac++] = anyaddr;
	nav[nac++] = av[0];

	return f_deleteallsadst(nac, nav);
}

#ifdef ENABLE_HYBRID
static vchar_t *
f_logoutusr(ac, av)
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com *head;
	char *user;
	size_t userlen;

	/* need username */
	if (ac < 1)
		errx(1, "insufficient arguments");
	user = av[0];
	userlen = strlen(user);
	if ((user == NULL) || (userlen > LOGINLEN))
		errx(1, "bad login (too long?)");

	buf = vmalloc(sizeof(*head) + userlen);
	if (buf == NULL)
		return NULL;

	head = (struct admin_com *)buf->v;
	head->ac_len = buf->l;
	head->ac_cmd = ADMIN_LOGOUT_USER;
	head->ac_errno = 0;
	head->ac_proto = 0;

	strncpy((char *)(head + 1), user, userlen);

	return buf;
}
#endif /* ENABLE_HYBRID */


static int
get_proto(str)
	char *str;
{
	struct proto_tag *cp;

	if (str == NULL) {
		errno = EINVAL;
		return -1;
	}

	/* checking the string of command. */
	for (cp = &prototab[0]; cp->str; cp++) {
		if (strcmp(str, cp->str) == 0)
			return cp->proto;
	}

	errno = EINVAL;
	return -1;
}

static vchar_t *
get_index(ac, av)
	int ac;
	char **av;
{
	int family;

	if (ac != 3 && ac != 4) {
		errno = EINVAL;
		return NULL;
	}

	/* checking the string of family */
	family = get_family(*av);
	if (family == -1)
		return NULL;
	av++;
	ac--;

	return get_comindexes(family, ac, av);
}

static int
get_family(str)
	char *str;
{
	if (strcmp("inet", str) == 0)
		return AF_INET;
#ifdef INET6
	else if (strcmp("inet6", str) == 0)
		return AF_INET6;
#endif
	errno = EAFNOSUPPORT;
	return -1;
}

static vchar_t *
get_comindexes(family, ac, av)
	int family;
	int ac;
	char **av;
{
	vchar_t *buf;
	struct admin_com_indexes *ci;
	char *p_name = NULL, *p_port = NULL;
	char *p_prefs = NULL, *p_prefd = NULL;
	struct sockaddr *src = NULL, *dst = NULL;
	int ulproto;

	if (ac != 2 && ac != 3) {
		errno = EINVAL;
		return NULL;
	}

	if (get_comindex(*av, &p_name, &p_port, &p_prefs) == -1)
		goto bad;
	src = get_sockaddr(family, p_name, p_port);
	if (p_name) {
		racoon_free(p_name);
		p_name = NULL;
	}
	if (p_port) {
		racoon_free(p_port);
		p_port = NULL;
	}
	if (src == NULL)
		goto bad;
	av++;
	ac--;
	if (get_comindex(*av, &p_name, &p_port, &p_prefd) == -1)
		goto bad;
	dst = get_sockaddr(family, p_name, p_port);
	if (p_name) {
		racoon_free(p_name);
		p_name = NULL;
	}
	if (p_port) {
		racoon_free(p_port);
		p_port = NULL;
	}
	if (dst == NULL)
		goto bad;

	buf = vmalloc(sizeof(*ci));
	if (buf == NULL)
		goto bad;

	av++;
	ac--;
	if(ac){
		ulproto = get_ulproto(*av);
		if (ulproto == -1)
			goto bad;
	}else
		ulproto=0;

	ci = (struct admin_com_indexes *)buf->v;
	if(p_prefs)
		ci->prefs = (u_int8_t)atoi(p_prefs); /* XXX should be handled error. */
	else
		ci->prefs = 32;
	if(p_prefd)
		ci->prefd = (u_int8_t)atoi(p_prefd); /* XXX should be handled error. */
	else
		ci->prefd = 32;
	ci->ul_proto = ulproto;
	memcpy(&ci->src, src, sysdep_sa_len(src));
	memcpy(&ci->dst, dst, sysdep_sa_len(dst));

	if (p_name)
		racoon_free(p_name);

	return buf;

   bad:
	if (p_name)
		racoon_free(p_name);
	if (p_port)
		racoon_free(p_port);
	if (p_prefs)
		racoon_free(p_prefs);
	if (p_prefd)
		racoon_free(p_prefd);
	return NULL;
}

static int
get_comindex(str, name, port, pref)
	char *str, **name, **port, **pref;
{
	char *p;

	*name = *port = *pref = NULL;

	*name = racoon_strdup(str);
	STRDUP_FATAL(*name);
	p = strpbrk(*name, "/[");
	if (p != NULL) {
		if (*(p + 1) == '\0')
			goto bad;
		if (*p == '/') {
			*p = '\0';
			*pref = racoon_strdup(p + 1);
			STRDUP_FATAL(*pref);
			p = strchr(*pref, '[');
			if (p != NULL) {
				if (*(p + 1) == '\0')
					goto bad;
				*p = '\0';
				*port = racoon_strdup(p + 1);
				STRDUP_FATAL(*port);
				p = strchr(*pref, ']');
				if (p == NULL)
					goto bad;
				*p = '\0';
			}
		} else if (*p == '[') {
			if (*pref == NULL)
				goto bad;
			*p = '\0';
			*port = racoon_strdup(p + 1);
			STRDUP_FATAL(*port);
			p = strchr(*pref, ']');
			if (p == NULL)
				goto bad;
			*p = '\0';
		} else {
			/* XXX */
		}
	}

	return 0;

    bad:

	if (*name)
		racoon_free(*name);
	if (*port)
		racoon_free(*port);
	if (*pref)
		racoon_free(*pref);
	*name = *port = *pref = NULL;
	return -1;
}

static int
get_ulproto(str)
	char *str;
{
	struct ulproto_tag *cp;

	if(str == NULL){
		errno = EINVAL;
		return -1;
	}

	/* checking the string of upper layer protocol. */
	for (cp = &ulprototab[0]; cp->str; cp++) {
		if (strcmp(str, cp->str) == 0)
			return cp->ul_proto;
	}

	errno = EINVAL;
	return -1;
}

/* %%% */
void
dump_isakmp_sa(buf, len)
	char *buf;
	int len;
{
	struct ph1dump *pd;
	struct tm *tm;
	char tbuf[56];
	caddr_t p = NULL;

/* isakmp status header */
/* short header;
 1234567890123456789012 0000000000000000:0000000000000000 000000000000
*/
char *header1 = 
"Destination            Cookies                           Created";

/* semi long header;
 1234567890123456789012 0000000000000000:0000000000000000 00 X 00 X 0000-00-00 00:00:00 000000
*/
char *header2 = 
"Destination            Cookies                           ST S  V E Created             Phase2";

/* long header;
 0000:0000:0000:0000:0000:0000:0000:0000.00000 0000:0000:0000:0000:0000:0000:0000:0000.00000 0000000000000000:0000000000000000 00 X 00 X 0000-00-00 00:00:00 000000
*/
char *header3 =
"Source                                        Destination                                   Cookies                           ST S  V E Created             Phase2";

/* phase status header */
/* short format;
   side stats source address         destination address   
   xxx  xxxxx 1234567890123456789012 1234567890123456789012
*/

	static char *estr[] = { "", "B", "M", "U", "A", "I", };

	switch (long_format) {
	case 0:
		printf("%s\n", header1);
		break;
	case 1:
		printf("%s\n", header2);
		break;
	case 2:
	default:
		printf("%s\n", header3);
		break;
	}

	if (len % sizeof(*pd))
		printf("invalid length %d\n", len);
	len /= sizeof(*pd);

	pd = (struct ph1dump *)buf;

	while (len-- > 0) {
		/* source address */
		if (long_format >= 2) {
			GETNAMEINFO((struct sockaddr *)&pd->local, _addr1_, _addr2_);
			switch (long_format) {
			case 0:
				break;
			case 1:
				p = fixed_addr(_addr1_, _addr2_, 22);
				break;
			case 2:
			default:
				p = fixed_addr(_addr1_, _addr2_, 45);
				break;
			}
			printf("%s ", p);
		}

		/* destination address */
		GETNAMEINFO((struct sockaddr *)&pd->remote, _addr1_, _addr2_);
		switch (long_format) {
		case 0:
		case 1:
			p = fixed_addr(_addr1_, _addr2_, 22);
			break;
		case 2:
		default:
			p = fixed_addr(_addr1_, _addr2_, 45);
			break;
		}
		printf("%s ", p);

		printf("%s ", pindex_isakmp(&pd->index));

		/* statuc, side and version */
		if (long_format >= 1) {
			printf("%2d %c %2x ",
				pd->status,
				pd->side == INITIATOR ? 'I' : 'R',
				pd->version);
			if (ARRAYLEN(estr) > pd->etype)
				printf("%s ", estr[pd->etype]);
		}

		/* created date */
		if (pd->created) {
			tm = localtime(&pd->created);
			strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", tm);
		} else
			snprintf(tbuf, sizeof(tbuf), "                   ");
		printf("%s ", tbuf);

		/* counter of phase 2 */
		if (long_format >= 1)
			printf("%6d ", pd->ph2cnt);

		printf("\n");

		pd++;
	}

	return;
}

/* %%% */
void
dump_internal(buf, tlen)
	char *buf;
	int tlen;
{
	struct ph2handle *iph2;
	struct sockaddr *addr;

/*
short header;
 source address         destination address    
 1234567890123456789012 1234567890123456789012 
*/
char *short_h1 = 
"Source                 Destination            ";

/*
long header;
 source address                                destination address                           
 123456789012345678901234567890123456789012345 123456789012345678901234567890123456789012345 
 0000:0000:0000:0000:0000:0000:0000:0000.00000 0000:0000:0000:0000:0000:0000:0000:0000.00000 0000:0000:0000:0000:0000:0000:0000:0000.00000
*/
char *long_h1 = 
"Source                                        Destination                                  ";

	printf("%s\n", long_format ? long_h1 : short_h1);

	while (tlen > 0) {
		iph2 = (struct ph2handle *)buf;
		addr = (struct sockaddr *)(++iph2);

		GETNAMEINFO(addr, _addr1_, _addr2_);
		printf("%s ", long_format ?
			  fixed_addr(_addr1_, _addr2_, 45)
			: fixed_addr(_addr1_, _addr2_, 22));
		addr++;
		tlen -= sysdep_sa_len(addr);

		GETNAMEINFO(addr, _addr1_, _addr2_);
		printf("%s ", long_format ?
			  fixed_addr(_addr1_, _addr2_, 45)
			: fixed_addr(_addr1_, _addr2_, 22));
		addr++;
		tlen -= sysdep_sa_len(addr);

		printf("\n");
	}

	return;
}

/* %%% */
char *
pindex_isakmp(index)
	isakmp_index *index;
{
	static char buf[64];
	u_char *p;
	int i, j;

	memset(buf, 0, sizeof(buf));

	/* copy index */
	p = (u_char *)index;
	for (j = 0, i = 0; i < sizeof(isakmp_index); i++) {
		snprintf((char *)&buf[j], sizeof(buf) - j, "%02x", p[i]);
		j += 2;
		switch (i) {
		case 7:
#if 0
		case 15:
#endif
			buf[j++] = ':';
		}
	}

	return buf;
}

/* print schedule */
char *str_sched_stat[] = {
"off",
"on",
"dead",
};

char *str_sched_id[] = {
"PH1resend",
"PH1lifetime",
"PH2resend",
"PSTacquire",
"PSTlifetime",
};

void
print_schedule(buf, len)
	caddr_t buf;
	int len;
{
	struct scheddump *sc = (struct scheddump *)buf;
	struct tm *tm;
	char tbuf[56];

	if (len % sizeof(*sc))
		printf("invalid length %d\n", len);
	len /= sizeof(*sc);

	/*      00000000 00000000 00000000 xxx........*/
	printf("index    tick     xtime    created\n");

	while (len-- > 0) {
		tm = localtime(&sc->created);
		strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", tm);

		printf("%-8ld %-8ld %-8ld %s\n",
			sc->id,
			(long)sc->tick,
			(long)sc->xtime,
			tbuf);
		sc++;
	}

	return;
}


void
print_evt(buf, len)
	caddr_t buf;
	int len;
{
	struct evtdump *evtdump = (struct evtdump *)buf;
	int i;
	char *srcstr;
	char *dststr;
	
	for (i = 0; evtmsg[i].msg; i++)
		if (evtmsg[i].type == evtdump->type)
			break;				
	
	if (evtmsg[i].msg == NULL) 
		printf("Event %d: ", evtdump->type);
	else
		printf("%s : ", evtmsg[i].msg);

	if ((srcstr = saddr2str((struct sockaddr *)&evtdump->src)) == NULL)
		printf("unknown");
	else 
		printf("%s", srcstr);
	printf(" -> ");
	if ((dststr = saddr2str((struct sockaddr *)&evtdump->dst)) == NULL)
		printf("unknown");
	else 
		printf("%s", dststr);
	printf("\n");

	return;
}

void
print_err(buf, len)
	caddr_t buf;
	int len;
{
	struct evtdump *evtdump = (struct evtdump *)buf;
	int i;
	
	
	for (i = 0; evtmsg[i].msg; i++)
		if (evtmsg[i].type == evtdump->type)
			break;				

	if (evtmsg[i].level != ERROR)
		return;
	
	if (evtmsg[i].msg == NULL) 
		printf("Error: Event %d\n", evtdump->type);
	else
		printf("Error: %s\n", evtmsg[i].msg);

	if (evt_filter & EVTF_ERR_STOP)
		evt_filter &= ~EVTF_LOOP;

	return;
}

/*
 * Print a message when phase 1 SA goes down
 */
void
print_ph1down(buf, len)
	caddr_t buf;
	int len;
{
	struct evtdump *evtdump = (struct evtdump *)buf;
	
	if (evtdump->type != EVTT_PHASE1_DOWN)
		return;

	printf("VPN connexion terminated\n");

	if (evt_filter & EVTF_PH1DOWN_STOP)
		evt_filter &= ~EVTF_LOOP;
	
	return;
}

/*
 * Print ISAKMP mode config info (IP and banner)
 */
void
print_cfg(buf, len)
	caddr_t buf;
	int len;
{
	struct evtdump *evtdump = (struct evtdump *)buf;
	struct isakmp_data *attr;
	char *banner = NULL;
	struct in_addr addr4;
	
	memset(&addr4, 0, sizeof(addr4));

	if (evtdump->type != EVTT_ISAKMP_CFG_DONE && 
	    evtdump->type != EVTT_NO_ISAKMP_CFG)
		return;

	len -= sizeof(*evtdump);
	attr = (struct isakmp_data *)(evtdump + 1);

	while (len > 0) {
		if (len < sizeof(*attr)) {
			printf("short attribute too short\n");
			break;
		}

		if ((ntohs(attr->type) & ISAKMP_GEN_MASK) == ISAKMP_GEN_TV) {
			/* Short attribute, skip */
			len -= sizeof(*attr);
			attr++;
		} else { /* Long attribute */
			char *n;

			if (len < (sizeof(*attr) + ntohs(attr->lorv))) {
				printf("long attribute too long\n");
				break;
			}

			switch (ntohs(attr->type) & ~ISAKMP_GEN_MASK) {
			case INTERNAL_IP4_ADDRESS:
				if (ntohs(attr->lorv) < sizeof(addr4)) {
					printf("addr4 attribute too short\n");
					break;
				}
				memcpy(&addr4, attr + 1, sizeof(addr4));
				break;

			case UNITY_BANNER:
				banner = racoon_malloc(ntohs(attr->lorv) + 1);
				if (banner == NULL) {
					printf("malloc failed\n");
					break;
				}
				memcpy(banner, attr + 1, ntohs(attr->lorv));
				banner[ntohs(attr->lorv)] = '\0';
				break;

			default:
				break;
			}

			len -= (sizeof(*attr) + ntohs(attr->lorv));
			n = (char *)attr;
			attr = (struct isakmp_data *)
			    (n + sizeof(*attr) + ntohs(attr->lorv));
		}
	}
	
	if (evtdump->type == EVTT_ISAKMP_CFG_DONE)
		printf("Bound to address %s\n", inet_ntoa(addr4));
	else
		printf("VPN connexion established\n");
	
	if (banner) {
		struct winsize win;
		int col = 0;
		int i;

		if (ioctl(1, TIOCGWINSZ, &win) != 1) 
			col = win.ws_col;
			
		for (i = 0; i < col; i++)
			printf("%c", '=');
		printf("\n%s\n", banner);
		for (i = 0; i < col; i++)
			printf("%c", '=');
		printf("\n");
		racoon_free(banner);
	}
	
	if (evt_filter & EVTF_CFG_STOP)
		evt_filter &= ~EVTF_LOOP;
	
	return;
}
	

char *
fixed_addr(addr, port, len)
	char *addr, *port;
	int len;
{
	static char _addr_buf_[BUFSIZ];
	char *p;
	int plen, i;

	/* initialize */
	memset(_addr_buf_, ' ', sizeof(_addr_buf_));

	plen = strlen(port);
	if (len < plen + 1)
		return NULL;

	p = _addr_buf_;
	for (i = 0; i < len - plen - 1 && addr[i] != '\0'; /*noting*/)
		*p++ = addr[i++];
	*p++ = '.';

	for (i = 0; i < plen && port[i] != '\0'; /*noting*/)
		*p++ = port[i++];

	_addr_buf_[len] = '\0';

	return _addr_buf_;
}

static int
handle_recv(combuf)
	vchar_t *combuf;
{
        struct admin_com h, *com;
        caddr_t buf;
        int len;

	com = (struct admin_com *)combuf->v;
	len = com->ac_len - sizeof(*com);
	buf = combuf->v + sizeof(*com);

	switch (com->ac_cmd) {
	case ADMIN_SHOW_SCHED:
		print_schedule(buf, len);
		break;

	case ADMIN_SHOW_EVT: {
		struct evtdump *evtdump;

		/* We got no event */
		if (len == 0) {
			/* If we were purging the queue, it is now done */
			if (evt_filter & EVTF_PURGE)
				evt_filter &= ~EVTF_PURGE;
			break;
		}

		if (len < sizeof(struct evtdump))
			errx(1, "Short buffer\n");		

		/* Toss outdated events */
		evtdump = (struct evtdump *)buf;
		if (evtdump->timestamp < evt_start)
			break;

		if (evt_filter & EVTF_ALL)
			print_evt(buf, len);
		if (evt_filter & EVTF_ERR)
			print_err(buf, len);
		if (evt_filter & EVTF_CFG)
			print_cfg(buf, len);
		if (evt_filter & EVTF_PH1DOWN)
			print_ph1down(buf, len);
		break;
	}

	case ADMIN_SHOW_SA:
	   {
		switch (com->ac_proto) {
		case ADMIN_PROTO_ISAKMP:
			dump_isakmp_sa(buf, len);
			break;
		case ADMIN_PROTO_IPSEC:
		case ADMIN_PROTO_AH:
		case ADMIN_PROTO_ESP:
		    {
			struct sadb_msg *msg = (struct sadb_msg *)buf;

			switch (msg->sadb_msg_errno) {
			case ENOENT:
				switch (msg->sadb_msg_type) {
				case SADB_DELETE:
				case SADB_GET:
					printf("No entry.\n");
					break;
				case SADB_DUMP:
					printf("No SAD entries.\n");
					break;
				}
				break;
			case 0:
				while (1) {
					pfkey_sadump(msg);
					if (msg->sadb_msg_seq == 0)
						break;
					msg = (struct sadb_msg *)((caddr_t)msg +
						     PFKEY_UNUNIT64(msg->sadb_msg_len));
				}
				break;
			default:
				printf("%s.\n", strerror(msg->sadb_msg_errno));
			}
		    }
			break;
		case ADMIN_PROTO_INTERNAL:
			dump_internal(buf, len);
			break;
		default:
			printf("Invalid proto [%d]\n", com->ac_proto);
		}

	    }
		break;

	default:
		/* IGNORE */
		break;
	}

	close(so);
	return 0;

    bad:
	close(so);
	return -1;
}