/*	$KAME: pfkey.c,v 1.132 2001/10/19 05:31:22 sakane 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.
 */

#define _PFKEY_C_

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

#include <net/route.h>
#include <net/pfkeyv2.h>
#include <netkey/key_debug.h>

#include <netinet/in.h>
#ifdef IPV6_INRIA_VERSION
#include <netinet/ipsec.h>
#else
#include <netinet6/ipsec.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>

#include "libpfkey.h"

#include "var.h"
#include "misc.h"
#include "vmbuf.h"
#include "plog.h"
#include "sockmisc.h"
#include "debug.h"

#include "schedule.h"
#include "localconf.h"
#include "remoteconf.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "isakmp_inf.h"
#include "ipsec_doi.h"
#include "oakley.h"
#include "pfkey.h"
#include "handler.h"
#include "policy.h"
#include "algorithm.h"
#include "sainfo.h"
#include "proposal.h"
#include "admin.h"
#include "strnames.h"
#include "backupsa.h"
#include "gcmalloc.h"

/* prototype */
static u_int ipsecdoi2pfkey_aalg __P((u_int));
static u_int ipsecdoi2pfkey_ealg __P((u_int));
static u_int ipsecdoi2pfkey_calg __P((u_int));
static u_int ipsecdoi2pfkey_alg __P((u_int, u_int));
static u_int keylen_aalg __P((u_int));
static u_int keylen_ealg __P((u_int, int));

static int pk_recvgetspi __P((caddr_t *));
static int pk_recvupdate __P((caddr_t *));
static int pk_recvadd __P((caddr_t *));
static int pk_recvdelete __P((caddr_t *));
static int pk_recvacquire __P((caddr_t *));
static int pk_recvexpire __P((caddr_t *));
static int pk_recvflush __P((caddr_t *));
static int getsadbpolicy __P((caddr_t *, int *, int, struct ph2handle *));
static int pk_recvspdupdate __P((caddr_t *));
static int pk_recvspdadd __P((caddr_t *));
static int pk_recvspddelete __P((caddr_t *));
static int pk_recvspdexpire __P((caddr_t *));
static int pk_recvspdget __P((caddr_t *));
static int pk_recvspddump __P((caddr_t *));
static int pk_recvspdflush __P((caddr_t *));
static struct sadb_msg *pk_recv __P((int, int *));

static int (*pkrecvf[]) __P((caddr_t *)) = {
NULL,
pk_recvgetspi,
pk_recvupdate,
pk_recvadd,
pk_recvdelete,
NULL,	/* SADB_GET */
pk_recvacquire,
NULL,	/* SABD_REGISTER */
pk_recvexpire,
pk_recvflush,
NULL,	/* SADB_DUMP */
NULL,	/* SADB_X_PROMISC */
NULL,	/* SADB_X_PCHANGE */
pk_recvspdupdate,
pk_recvspdadd,
pk_recvspddelete,
pk_recvspdget,
NULL,	/* SADB_X_SPDACQUIRE */
pk_recvspddump,
pk_recvspdflush,
NULL,	/* SADB_X_SPDSETIDX */
pk_recvspdexpire,
NULL,	/* SADB_X_SPDDELETE2 */
};

static int addnewsp __P((caddr_t *));

/* cope with old kame headers - ugly */
#ifndef SADB_X_AALG_MD5
#define SADB_X_AALG_MD5		SADB_AALG_MD5	
#endif
#ifndef SADB_X_AALG_SHA
#define SADB_X_AALG_SHA		SADB_AALG_SHA
#endif
#ifndef SADB_X_AALG_NULL
#define SADB_X_AALG_NULL	SADB_AALG_NULL
#endif

#ifndef SADB_X_EALG_BLOWFISHCBC
#define SADB_X_EALG_BLOWFISHCBC	SADB_EALG_BLOWFISHCBC
#endif
#ifndef SADB_X_EALG_CAST128CBC
#define SADB_X_EALG_CAST128CBC	SADB_EALG_CAST128CBC
#endif
#ifndef SADB_X_EALG_RC5CBC
#ifdef SADB_EALG_RC5CBC
#define SADB_X_EALG_RC5CBC	SADB_EALG_RC5CBC
#endif
#endif

/*
 * PF_KEY packet handler
 *	0: success
 *	-1: fail
 */
int
pfkey_handler()
{
	struct sadb_msg *msg;
	int len;
	caddr_t mhp[SADB_EXT_MAX + 1];
	int error = -1;

	/* receive pfkey message. */
	len = 0;
	msg = (struct sadb_msg *)pk_recv(lcconf->sock_pfkey, &len);
	if (msg == NULL) {
		if (len < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to recv from pfkey (%s)\n",
				strerror(errno));
			goto end;
		} else {
			/* short message - msg not ready */
			return 0;
		}
	}

	plog(LLV_DEBUG, LOCATION, NULL, "get pfkey %s message\n",
		s_pfkey_type(msg->sadb_msg_type));
	plogdump(LLV_DEBUG2, msg, msg->sadb_msg_len << 3);

	/* validity check */
	if (msg->sadb_msg_errno) {
		int pri;

		/* when SPD is empty, treat the state as no error. */
		if (msg->sadb_msg_type == SADB_X_SPDDUMP &&
		    msg->sadb_msg_errno == ENOENT)
			pri = LLV_DEBUG;
		else
			pri = LLV_ERROR;

		plog(pri, LOCATION, NULL,
			"pfkey %s failed: %s\n",
			s_pfkey_type(msg->sadb_msg_type),
			strerror(msg->sadb_msg_errno));

		goto end;
	}

	/* check pfkey message. */
	if (pfkey_align(msg, mhp)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed pfkey align (%s)\n",
			ipsec_strerror());
		goto end;
	}
	if (pfkey_check(mhp)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed pfkey check (%s)\n",
			ipsec_strerror());
		goto end;
	}
	msg = (struct sadb_msg *)mhp[0];

	if (pkrecvf[msg->sadb_msg_type] == NULL) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"not supported command %s\n",
			s_pfkey_type(msg->sadb_msg_type));
		goto end;
	}

	if ((pkrecvf[msg->sadb_msg_type])(mhp) < 0)
		goto end;

	error = 0;
end:
	if (msg)
		racoon_free(msg);
	return(error);
}

/*
 * dump SADB
 */
vchar_t *
pfkey_dump_sadb(satype)
	int satype;
{
	int s = -1;
	vchar_t *buf = NULL;
	pid_t pid = getpid();
	struct sadb_msg *msg = NULL;
	size_t bl, ml;
	int len;

	if ((s = pfkey_open()) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed pfkey open: %s\n",
			ipsec_strerror());
		return NULL;
	}

	plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_dump\n");
	if (pfkey_send_dump(s, satype) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed dump: %s\n", ipsec_strerror());
		goto fail;
	}

	while (1) {
		if (msg)
			racoon_free(msg);
		msg = pk_recv(s, &len);
		if (msg == NULL) {
			if (len < 0)
				goto done;
			else
				continue;
		}

		if (msg->sadb_msg_type != SADB_DUMP || msg->sadb_msg_pid != pid)
			continue;

		ml = msg->sadb_msg_len << 3;
		bl = buf ? buf->l : 0;
		buf = vrealloc(buf, bl + ml);
		if (buf == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to reallocate buffer to dump.\n");
			goto fail;
		}
		memcpy(buf->v + bl, msg, ml);

		if (msg->sadb_msg_seq == 0)
			break;
	}
	goto done;

fail:
	if (buf)
		vfree(buf);
	buf = NULL;
done:
	if (msg)
		racoon_free(msg);
	if (s >= 0)
		close(s);
	return buf;
}

/*
 * flush SADB
 */
void
pfkey_flush_sadb(proto)
	u_int proto;
{
	int satype;

	/* convert to SADB_SATYPE */
	if ((satype = admin2pfkey_proto(proto)) < 0)
		return;

	plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_flush\n");
	if (pfkey_send_flush(lcconf->sock_pfkey, satype) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed send flush (%s)\n", ipsec_strerror());
		return;
	}

	return;
}

/*
 * These are the SATYPEs that we manage.  We register to get
 * PF_KEY messages related to these SATYPEs, and we also use
 * this list to determine which SATYPEs to delete SAs for when
 * we receive an INITIAL-CONTACT.
 */
const struct pfkey_satype pfkey_satypes[] = {
	{ SADB_SATYPE_AH,	"AH" },
	{ SADB_SATYPE_ESP,	"ESP" },
	{ SADB_X_SATYPE_IPCOMP,	"IPCOMP" },
};
const int pfkey_nsatypes =
    sizeof(pfkey_satypes) / sizeof(pfkey_satypes[0]);

/*
 * PF_KEY initialization
 */
int
pfkey_init()
{
	int i, reg_fail;

	if ((lcconf->sock_pfkey = pfkey_open()) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed pfkey open (%s)", ipsec_strerror());
		return -1;
	}

	for (i = 0, reg_fail = 0; i < pfkey_nsatypes; i++) {
		plog(LLV_DEBUG, LOCATION, NULL,
		    "call pfkey_send_register for %s\n",
		    pfkey_satypes[i].ps_name);
		if (pfkey_send_register(lcconf->sock_pfkey,
					pfkey_satypes[i].ps_satype) < 0 ||
		    pfkey_recv_register(lcconf->sock_pfkey) < 0) {
			plog(LLV_WARNING, LOCATION, NULL,
			    "failed to register %s (%s)",
			    pfkey_satypes[i].ps_name,
			    ipsec_strerror());
			reg_fail++;
		}
	}

	if (reg_fail == pfkey_nsatypes) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to regist any protocol.");
		pfkey_close(lcconf->sock_pfkey);
		return -1;
	}

	initsp();

	if (pfkey_send_spddump(lcconf->sock_pfkey) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec sending spddump failed: %s",
			ipsec_strerror());
		pfkey_close(lcconf->sock_pfkey);
		return -1;
	}
#if 0
	if (pfkey_promisc_toggle(1) < 0) {
		pfkey_close(lcconf->sock_pfkey);
		return -1;
	}
#endif
	return 0;
}

/* %%% for conversion */
/* IPSECDOI_ATTR_AUTH -> SADB_AALG */
static u_int
ipsecdoi2pfkey_aalg(hashtype)
	u_int hashtype;
{
	switch (hashtype) {
	case IPSECDOI_ATTR_AUTH_HMAC_MD5:
		return SADB_AALG_MD5HMAC;
	case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
		return SADB_AALG_SHA1HMAC;
	case IPSECDOI_ATTR_AUTH_KPDK:		/* need special care */
		return SADB_AALG_NONE;

	/* not supported */
	case IPSECDOI_ATTR_AUTH_DES_MAC:
		plog(LLV_ERROR, LOCATION, NULL,
			"Not supported hash type: %u\n", hashtype);
		return ~0;

	case 0: /* reserved */
	default:
		return SADB_AALG_NONE;

		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid hash type: %u\n", hashtype);
		return ~0;
	}
	/*NOTREACHED*/
}

/* IPSECDOI_ESP -> SADB_EALG */
static u_int
ipsecdoi2pfkey_ealg(t_id)
	u_int t_id;
{
	switch (t_id) {
	case IPSECDOI_ESP_DES_IV64:		/* sa_flags |= SADB_X_EXT_OLD */
		return SADB_EALG_DESCBC;
	case IPSECDOI_ESP_DES:
		return SADB_EALG_DESCBC;
	case IPSECDOI_ESP_3DES:
		return SADB_EALG_3DESCBC;
#ifdef SADB_X_EALG_RC5CBC
	case IPSECDOI_ESP_RC5:
		return SADB_X_EALG_RC5CBC;
#endif
	case IPSECDOI_ESP_CAST:
		return SADB_X_EALG_CAST128CBC;
	case IPSECDOI_ESP_BLOWFISH:
		return SADB_X_EALG_BLOWFISHCBC;
	case IPSECDOI_ESP_DES_IV32:	/* flags |= (SADB_X_EXT_OLD|
							SADB_X_EXT_IV4B)*/
		return SADB_EALG_DESCBC;
	case IPSECDOI_ESP_NULL:
		return SADB_EALG_NULL;
#ifdef SADB_X_EALG_RIJNDAELCBC
	case IPSECDOI_ESP_RIJNDAEL:
		return SADB_X_EALG_RIJNDAELCBC;
#endif
#ifdef SADB_X_EALG_TWOFISHCBC
	case IPSECDOI_ESP_TWOFISH:
		return SADB_X_EALG_TWOFISHCBC;
#endif

	/* not supported */
	case IPSECDOI_ESP_3IDEA:
	case IPSECDOI_ESP_IDEA:
	case IPSECDOI_ESP_RC4:
		plog(LLV_ERROR, LOCATION, NULL,
			"Not supported transform: %u\n", t_id);
		return ~0;

	case 0: /* reserved */
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid transform id: %u\n", t_id);
		return ~0;
	}
	/*NOTREACHED*/
}

/* IPCOMP -> SADB_CALG */
static u_int
ipsecdoi2pfkey_calg(t_id)
	u_int t_id;
{
	switch (t_id) {
	case IPSECDOI_IPCOMP_OUI:
		return SADB_X_CALG_OUI;
	case IPSECDOI_IPCOMP_DEFLATE:
		return SADB_X_CALG_DEFLATE;
	case IPSECDOI_IPCOMP_LZS:
		return SADB_X_CALG_LZS;

	case 0: /* reserved */
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid transform id: %u\n", t_id);
		return ~0;
	}
	/*NOTREACHED*/
}

/* IPSECDOI_PROTO -> SADB_SATYPE */
u_int
ipsecdoi2pfkey_proto(proto)
	u_int proto;
{
	switch (proto) {
	case IPSECDOI_PROTO_IPSEC_AH:
		return SADB_SATYPE_AH;
	case IPSECDOI_PROTO_IPSEC_ESP:
		return SADB_SATYPE_ESP;
	case IPSECDOI_PROTO_IPCOMP:
		return SADB_X_SATYPE_IPCOMP;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid ipsec_doi proto: %u\n", proto);
		return ~0;
	}
	/*NOTREACHED*/
}

static u_int
ipsecdoi2pfkey_alg(algclass, type)
	u_int algclass, type;
{
	switch (algclass) {
	case IPSECDOI_ATTR_AUTH:
		return ipsecdoi2pfkey_aalg(type);
	case IPSECDOI_PROTO_IPSEC_ESP:
		return ipsecdoi2pfkey_ealg(type);
	case IPSECDOI_PROTO_IPCOMP:
		return ipsecdoi2pfkey_calg(type);
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid ipsec_doi algclass: %u\n", algclass);
		return ~0;
	}
	/*NOTREACHED*/
}

/* SADB_SATYPE -> IPSECDOI_PROTO */
u_int
pfkey2ipsecdoi_proto(satype)
	u_int satype;
{
	switch (satype) {
	case SADB_SATYPE_AH:
		return IPSECDOI_PROTO_IPSEC_AH;
	case SADB_SATYPE_ESP:
		return IPSECDOI_PROTO_IPSEC_ESP;
	case SADB_X_SATYPE_IPCOMP:
		return IPSECDOI_PROTO_IPCOMP;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid pfkey proto: %u\n", satype);
		return ~0;
	}
	/*NOTREACHED*/
}

/* IPSECDOI_ATTR_ENC_MODE -> IPSEC_MODE */
u_int
ipsecdoi2pfkey_mode(mode)
	u_int mode;
{
	switch (mode) {
	case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
		return IPSEC_MODE_TUNNEL;
	case IPSECDOI_ATTR_ENC_MODE_TRNS:
		return IPSEC_MODE_TRANSPORT;
	default:
		plog(LLV_ERROR, LOCATION, NULL, "Invalid mode type: %u\n", mode);
		return ~0;
	}
	/*NOTREACHED*/
}

/* IPSECDOI_ATTR_ENC_MODE -> IPSEC_MODE */
u_int
pfkey2ipsecdoi_mode(mode)
	u_int mode;
{
	switch (mode) {
	case IPSEC_MODE_TUNNEL:
		return IPSECDOI_ATTR_ENC_MODE_TUNNEL;
	case IPSEC_MODE_TRANSPORT:
		return IPSECDOI_ATTR_ENC_MODE_TRNS;
	case IPSEC_MODE_ANY:
		return IPSECDOI_ATTR_ENC_MODE_ANY;
	default:
		plog(LLV_ERROR, LOCATION, NULL, "Invalid mode type: %u\n", mode);
		return ~0;
	}
	/*NOTREACHED*/
}

/* default key length for encryption algorithm */
static u_int
keylen_aalg(hashtype)
	u_int hashtype;
{
	int res;

	if (hashtype == 0)
		return SADB_AALG_NONE;

	res = alg_ipsec_hmacdef_hashlen(hashtype);
	if (res == -1) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid hmac algorithm %u.\n", hashtype);
		return ~0;
	}
	return res;
}

/* default key length for encryption algorithm */
static u_int
keylen_ealg(enctype, encklen)
	u_int enctype;
	int encklen;
{
	int res;

	res = alg_ipsec_encdef_keylen(enctype, encklen);
	if (res == -1) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid encryption algorithm %u.\n", enctype);
		return ~0;
	}
	return res;
}

int
pfkey_convertfromipsecdoi(proto_id, t_id, hashtype,
		e_type, e_keylen, a_type, a_keylen, flags)
	u_int proto_id;
	u_int t_id;
	u_int hashtype;
	u_int *e_type;
	u_int *e_keylen;
	u_int *a_type;
	u_int *a_keylen;
	u_int *flags;
{
	*flags = 0;
	switch (proto_id) {
	case IPSECDOI_PROTO_IPSEC_ESP:
		if ((*e_type = ipsecdoi2pfkey_ealg(t_id)) == ~0)
			goto bad;
		if ((*e_keylen = keylen_ealg(t_id, *e_keylen)) == ~0)
			goto bad;
		*e_keylen >>= 3;

		if ((*a_type = ipsecdoi2pfkey_aalg(hashtype)) == ~0)
			goto bad;
		if ((*a_keylen = keylen_aalg(hashtype)) == ~0)
			goto bad;
		*a_keylen >>= 3;

		if (*e_type == SADB_EALG_NONE) {
			plog(LLV_ERROR, LOCATION, NULL, "no ESP algorithm.\n");
			goto bad;
		}
		break;

	case IPSECDOI_PROTO_IPSEC_AH:
		if ((*a_type = ipsecdoi2pfkey_aalg(hashtype)) == ~0)
			goto bad;
		if ((*a_keylen = keylen_aalg(hashtype)) == ~0)
			goto bad;
		*a_keylen >>= 3;

		if (t_id == IPSECDOI_ATTR_AUTH_HMAC_MD5 
		 && hashtype == IPSECDOI_ATTR_AUTH_KPDK) {
			/* AH_MD5 + Auth(KPDK) = RFC1826 keyed-MD5 */
			*a_type = SADB_X_AALG_MD5;
			*flags |= SADB_X_EXT_OLD;
		}
		*e_type = SADB_EALG_NONE;
		*e_keylen = 0;
		if (*a_type == SADB_AALG_NONE) {
			plog(LLV_ERROR, LOCATION, NULL, "no AH algorithm.\n");
			goto bad;
		}
		break;

	case IPSECDOI_PROTO_IPCOMP:
		if ((*e_type = ipsecdoi2pfkey_calg(t_id)) == ~0)
			goto bad;
		*e_keylen = 0;

		*flags = SADB_X_EXT_RAWCPI;

		*a_type = SADB_AALG_NONE;
		*a_keylen = 0;
		if (*e_type == SADB_X_CALG_NONE) {
			plog(LLV_ERROR, LOCATION, NULL, "no IPCOMP algorithm.\n");
			goto bad;
		}
		break;

	default:
		plog(LLV_ERROR, LOCATION, NULL, "unknown IPsec protocol.\n");
		goto bad;
	}

	return 0;

    bad:
	errno = EINVAL;
	return -1;
}

/* called from scheduler */
void
pfkey_timeover_stub(p)
	void *p;
{

	pfkey_timeover((struct ph2handle *)p);
}

void
pfkey_timeover(iph2)
	struct ph2handle *iph2;
{
	plog(LLV_ERROR, LOCATION, NULL,
		"%s give up to get IPsec-SA due to time up to wait.\n",
		saddrwop2str(iph2->dst));
	SCHED_KILL(iph2->sce);

	/* If initiator side, send error to kernel by SADB_ACQUIRE. */
	if (iph2->side == INITIATOR)
		pk_sendeacquire(iph2);

	unbindph12(iph2);
	remph2(iph2);
	delph2(iph2);

	return;
}

/*%%%*/
/* send getspi message per ipsec protocol per remote address */
/*
 * the local address and remote address in ph1handle are dealed
 * with destination address and source address respectively.
 * Because SPI is decided by responder.
 */
int
pk_sendgetspi(iph2)
	struct ph2handle *iph2;
{
	u_int satype, mode;
	struct saprop *pp;
	struct saproto *pr;

	pp = iph2->side == INITIATOR
			? iph2->proposal
			: iph2->approval;

	for (pr = pp->head; pr != NULL; pr = pr->next) {

		/* validity check */
		satype = ipsecdoi2pfkey_proto(pr->proto_id);
		if (satype == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proto_id %d\n", pr->proto_id);
			return -1;
		}
		mode = ipsecdoi2pfkey_mode(pr->encmode);
		if (mode == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid encmode %d\n", pr->encmode);
			return -1;
		}

		plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_getspi\n");
		if (pfkey_send_getspi(
				lcconf->sock_pfkey,
				satype,
				mode,
				iph2->dst,		/* src of SA */
				iph2->src,		/* dst of SA */
				0, 0, pr->reqid_in, iph2->seq) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"ipseclib failed send getspi (%s)\n",
				ipsec_strerror());
			return -1;
		}
		plog(LLV_DEBUG, LOCATION, NULL,
			"pfkey GETSPI sent: %s\n",
			sadbsecas2str(iph2->dst, iph2->src, satype, 0, mode));
	}

	return 0;
}

/*
 * receive GETSPI from kernel.
 */
static int
pk_recvgetspi(mhp) 
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_sa *sa;
	struct ph2handle *iph2;
	struct sockaddr *dst;
	int proto_id;
	int allspiok, notfound;
	struct saprop *pp;
	struct saproto *pr;

	/* validity check */
	if (mhp[SADB_EXT_SA] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb getspi message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
	dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]); /* note SA dir */

	/* the message has to be processed or not ? */
	if (msg->sadb_msg_pid != getpid()) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"%s message is not interesting "
			"because pid %d is not mine.\n",
			s_pfkey_type(msg->sadb_msg_type),
			msg->sadb_msg_pid);
		return -1;
	}

	iph2 = getph2byseq(msg->sadb_msg_seq);
	if (iph2 == NULL) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"seq %d of %s message not interesting.\n",
			msg->sadb_msg_seq,
			s_pfkey_type(msg->sadb_msg_type));
		return -1;
	}

	if (iph2->status != PHASE2ST_GETSPISENT) {
		plog(LLV_ERROR, LOCATION, NULL,
			"status mismatch (db:%d msg:%d)\n",
			iph2->status, PHASE2ST_GETSPISENT);
		return -1;
	}

	/* set SPI, and check to get all spi whether or not */
	allspiok = 1;
	notfound = 1;
	proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
	pp = iph2->side == INITIATOR ? iph2->proposal : iph2->approval;

	for (pr = pp->head; pr != NULL; pr = pr->next) {
		if (pr->proto_id == proto_id && pr->spi == 0) {
			pr->spi = sa->sadb_sa_spi;
			notfound = 0;
			plog(LLV_DEBUG, LOCATION, NULL,
				"pfkey GETSPI succeeded: %s\n",
				sadbsecas2str(iph2->dst, iph2->src,
				    msg->sadb_msg_satype,
				    sa->sadb_sa_spi,
				    ipsecdoi2pfkey_mode(pr->encmode)));
		}
		if (pr->spi == 0)
			allspiok = 0;	/* not get all spi */
	}

	if (notfound) {
		plog(LLV_ERROR, LOCATION, NULL,
			"get spi for unknown address %s\n",
			saddrwop2str(iph2->dst));
		return -1;
	}

	if (allspiok) {
		/* update status */
		iph2->status = PHASE2ST_GETSPIDONE;
		if (isakmp_post_getspi(iph2) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to start post getspi.\n");
			unbindph12(iph2);
			remph2(iph2);
			delph2(iph2);
			iph2 = NULL;
			return -1;
		}
	}

	return 0;
}

/*
 * set inbound SA
 */
int
pk_sendupdate(iph2)
	struct ph2handle *iph2;
{
	struct saproto *pr;
	struct sockaddr *src = NULL, *dst = NULL;
	int e_type, e_keylen, a_type, a_keylen, flags;
	u_int satype, mode;
	u_int64_t lifebyte = 0;

	/* sanity check */
	if (iph2->approval == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no approvaled SAs found.\n");
	}

	/* for mobile IPv6 */
	if (iph2->ph1->rmconf->support_mip6 && iph2->src_id && iph2->dst_id) {
		src = iph2->src_id;
		dst = iph2->dst_id;
	} else {
		src = iph2->src;
		dst = iph2->dst;
	}

	for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
		/* validity check */
		satype = ipsecdoi2pfkey_proto(pr->proto_id);
		if (satype == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proto_id %d\n", pr->proto_id);
			return -1;
		}
#ifdef ENABLE_SAMODE_UNSPECIFIED
		mode = IPSEC_MODE_ANY;
#else
		mode = ipsecdoi2pfkey_mode(pr->encmode);
		if (mode == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid encmode %d\n", pr->encmode);
			return -1;
		}
#endif

		/* set algorithm type and key length */
		e_keylen = pr->head->encklen;
		if (pfkey_convertfromipsecdoi(
				pr->proto_id,
				pr->head->trns_id,
				pr->head->authtype,
				&e_type, &e_keylen,
				&a_type, &a_keylen, &flags) < 0)
			return -1;

#if 0
		lifebyte = iph2->approval->lifebyte * 1024,
#else
		lifebyte = 0;
#endif

		plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_update\n");

		if (pfkey_send_update(
				lcconf->sock_pfkey,
				satype,
				mode,
				iph2->dst,
				iph2->src,
				pr->spi,
				pr->reqid_in,
				4,	/* XXX static size of window */
				pr->keymat->v,
				e_type, e_keylen, a_type, a_keylen, flags,
				0, lifebyte, iph2->approval->lifetime, 0,
				iph2->seq) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"libipsec failed send update (%s)\n",
				ipsec_strerror());
			return -1;
		}

		if (!lcconf->pathinfo[LC_PATHTYPE_BACKUPSA])
			continue;

		/*
		 * It maybe good idea to call backupsa_to_file() after
		 * racoon will receive the sadb_update messages.
		 * But it is impossible because there is not key in the
		 * information from the kernel.
		 */
		if (backupsa_to_file(satype, mode, iph2->dst, iph2->src,
				pr->spi, pr->reqid_in, 4,
				pr->keymat->v,
				e_type, e_keylen, a_type, a_keylen, flags,
				0, iph2->approval->lifebyte * 1024,
				iph2->approval->lifetime, 0,
				iph2->seq) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"backuped SA failed: %s\n",
				sadbsecas2str(iph2->dst, iph2->src,
				satype, pr->spi, mode));
		}
		plog(LLV_DEBUG, LOCATION, NULL,
			"backuped SA: %s\n",
			sadbsecas2str(iph2->dst, iph2->src,
			satype, pr->spi, mode));
	}

	return 0;
}

static int
pk_recvupdate(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_sa *sa;
	struct sockaddr *src, *dst;
	struct ph2handle *iph2;
	u_int proto_id, encmode, sa_mode;
	int incomplete = 0;
	struct saproto *pr;

	/* ignore this message because of local test mode. */
	if (f_local)
		return 0;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_SA] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb update message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
	dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);
	sa = (struct sadb_sa *)mhp[SADB_EXT_SA];

	sa_mode = mhp[SADB_X_EXT_SA2] == NULL
		? IPSEC_MODE_ANY
		: ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

	/* the message has to be processed or not ? */
	if (msg->sadb_msg_pid != getpid()) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"%s message is not interesting "
			"because pid %d is not mine.\n",
			s_pfkey_type(msg->sadb_msg_type),
			msg->sadb_msg_pid);
		return -1;
	}

	iph2 = getph2byseq(msg->sadb_msg_seq);
	if (iph2 == NULL) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"seq %d of %s message not interesting.\n",
			msg->sadb_msg_seq,
			s_pfkey_type(msg->sadb_msg_type));
		return -1;
	}

	if (iph2->status != PHASE2ST_ADDSA) {
		plog(LLV_ERROR, LOCATION, NULL,
			"status mismatch (db:%d msg:%d)\n",
			iph2->status, PHASE2ST_ADDSA);
		return -1;
	}

	/* check to complete all keys ? */
	for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
		proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
		if (proto_id == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proto_id %d\n", msg->sadb_msg_satype);
			return -1;
		}
		encmode = pfkey2ipsecdoi_mode(sa_mode);
		if (encmode == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid encmode %d\n", sa_mode);
			return -1;
		}

		if (pr->proto_id == proto_id
		 && pr->spi == sa->sadb_sa_spi) {
			pr->ok = 1;
			plog(LLV_DEBUG, LOCATION, NULL,
				"pfkey UPDATE succeeded: %s\n",
				sadbsecas2str(iph2->dst, iph2->src,
				    msg->sadb_msg_satype,
				    sa->sadb_sa_spi,
				    sa_mode));

			plog(LLV_INFO, LOCATION, NULL,
				"IPsec-SA established: %s\n",
				sadbsecas2str(iph2->dst, iph2->src,
					msg->sadb_msg_satype, sa->sadb_sa_spi,
					sa_mode));
		}

		if (pr->ok == 0)
			incomplete = 1;
	}

	if (incomplete)
		return 0;

	/* turn off the timer for calling pfkey_timeover() */
	SCHED_KILL(iph2->sce);
	
	/* update status */
	iph2->status = PHASE2ST_ESTABLISHED;

#ifdef ENABLE_STATS
	gettimeofday(&iph2->end, NULL);
	syslog(LOG_NOTICE, "%s(%s): %8.6f",
		"phase2", "quick", timedelta(&iph2->start, &iph2->end));
#endif

	/* count up */
	iph2->ph1->ph2cnt++;

	/*
	 * since we are going to reuse the phase2 handler, we need to
	 * remain it and refresh all the references between ph1 and ph2 to use.
	 */
	unbindph12(iph2);

	iph2->sce = sched_new(iph2->approval->lifetime,
	    isakmp_ph2expire_stub, iph2);

	plog(LLV_DEBUG, LOCATION, NULL, "===\n");
	return 0;
}

/*
 * set outbound SA
 */
int
pk_sendadd(iph2)
	struct ph2handle *iph2;
{
	struct saproto *pr;
	struct sockaddr *src = NULL, *dst = NULL;
	int e_type, e_keylen, a_type, a_keylen, flags;
	u_int satype, mode;
	u_int64_t lifebyte = 0;

	/* sanity check */
	if (iph2->approval == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no approvaled SAs found.\n");
	}

	/* for mobile IPv6 */
	if (iph2->ph1->rmconf->support_mip6 && iph2->src_id && iph2->dst_id) {
		src = iph2->src_id;
		dst = iph2->dst_id;
	} else {
		src = iph2->src;
		dst = iph2->dst;
	}

	for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
		/* validity check */
		satype = ipsecdoi2pfkey_proto(pr->proto_id);
		if (satype == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proto_id %d\n", pr->proto_id);
			return -1;
		}
#ifdef ENABLE_SAMODE_UNSPECIFIED
		mode = IPSEC_MODE_ANY;
#else
		mode = ipsecdoi2pfkey_mode(pr->encmode);
		if (mode == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid encmode %d\n", pr->encmode);
			return -1;
		}
#endif

		/* set algorithm type and key length */
		e_keylen = pr->head->encklen;
		if (pfkey_convertfromipsecdoi(
				pr->proto_id,
				pr->head->trns_id,
				pr->head->authtype,
				&e_type, &e_keylen,
				&a_type, &a_keylen, &flags) < 0)
			return -1;

#if 0
		lifebyte = iph2->approval->lifebyte * 1024,
#else
		lifebyte = 0;
#endif

		plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_add\n");

		if (pfkey_send_add(
				lcconf->sock_pfkey,
				satype,
				mode,
				iph2->src,
				iph2->dst,
				pr->spi_p,
				pr->reqid_out,
				4,	/* XXX static size of window */
				pr->keymat_p->v,
				e_type, e_keylen, a_type, a_keylen, flags,
				0, lifebyte, iph2->approval->lifetime, 0,
				iph2->seq) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"libipsec failed send add (%s)\n",
				ipsec_strerror());
			return -1;
		}

		if (!lcconf->pathinfo[LC_PATHTYPE_BACKUPSA])
			continue;

		/*
		 * It maybe good idea to call backupsa_to_file() after
		 * racoon will receive the sadb_update messages.
		 * But it is impossible because there is not key in the
		 * information from the kernel.
		 */
		if (backupsa_to_file(satype, mode, iph2->src, iph2->dst,
				pr->spi_p, pr->reqid_out, 4,
				pr->keymat_p->v,
				e_type, e_keylen, a_type, a_keylen, flags,
				0, iph2->approval->lifebyte * 1024,
				iph2->approval->lifetime, 0,
				iph2->seq) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"backuped SA failed: %s\n",
				sadbsecas2str(iph2->src, iph2->dst,
				satype, pr->spi_p, mode));
		}
		plog(LLV_DEBUG, LOCATION, NULL,
			"backuped SA: %s\n",
			sadbsecas2str(iph2->src, iph2->dst,
			satype, pr->spi_p, mode));
	}

	return 0;
}

static int
pk_recvadd(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_sa *sa;
	struct sockaddr *src, *dst;
	struct ph2handle *iph2;
	u_int sa_mode;

	/* ignore this message because of local test mode. */
	if (f_local)
		return 0;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_SA] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb add message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
	dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);
	sa = (struct sadb_sa *)mhp[SADB_EXT_SA];

	sa_mode = mhp[SADB_X_EXT_SA2] == NULL
		? IPSEC_MODE_ANY
		: ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

	/* the message has to be processed or not ? */
	if (msg->sadb_msg_pid != getpid()) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"%s message is not interesting "
			"because pid %d is not mine.\n",
			s_pfkey_type(msg->sadb_msg_type),
			msg->sadb_msg_pid);
		return -1;
	}

	iph2 = getph2byseq(msg->sadb_msg_seq);
	if (iph2 == NULL) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"seq %d of %s message not interesting.\n",
			msg->sadb_msg_seq,
			s_pfkey_type(msg->sadb_msg_type));
		return -1;
	}

	/*
	 * NOTE don't update any status of phase2 handle
	 * because they must be updated by SADB_UPDATE message
	 */

	plog(LLV_INFO, LOCATION, NULL,
		"IPsec-SA established: %s\n",
		sadbsecas2str(iph2->src, iph2->dst,
			msg->sadb_msg_satype, sa->sadb_sa_spi, sa_mode));

	plog(LLV_DEBUG, LOCATION, NULL, "===\n");
	return 0;
}

static int
pk_recvexpire(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_sa *sa;
	struct sockaddr *src, *dst;
	struct ph2handle *iph2;
	u_int proto_id, sa_mode;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_SA] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || (mhp[SADB_EXT_LIFETIME_HARD] != NULL
	  && mhp[SADB_EXT_LIFETIME_SOFT] != NULL)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb expire message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
	src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
	dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

	sa_mode = mhp[SADB_X_EXT_SA2] == NULL
		? IPSEC_MODE_ANY
		: ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

	proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
	if (proto_id == ~0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid proto_id %d\n", msg->sadb_msg_satype);
		return -1;
	}

	plog(LLV_INFO, LOCATION, NULL,
		"IPsec-SA expired: %s\n",
		sadbsecas2str(src, dst,
			msg->sadb_msg_satype, sa->sadb_sa_spi, sa_mode));

	iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi);
	if (iph2 == NULL) {
		/*
		 * Ignore it because two expire messages are come up.
		 * phase2 handler has been deleted already when 2nd message
		 * is received.
		 */
		plog(LLV_DEBUG, LOCATION, NULL,
			"no such a SA found: %s\n",
			sadbsecas2str(src, dst,
			    msg->sadb_msg_satype, sa->sadb_sa_spi,
			    sa_mode));
		return 0;
	}
	if (iph2->status != PHASE2ST_ESTABLISHED) {
		/*
		 * If the status is not equal to PHASE2ST_ESTABLISHED,
		 * racoon ignores this expire message.  There are two reason.
		 * One is that the phase 2 probably starts becuase there is
		 * a potential that racoon receives the acquire message
		 * without receiving a expire message.  Another is that racoon
		 * may receive the multiple expire messages from the kernel.
		 */
		plog(LLV_WARNING, LOCATION, NULL,
			"the expire message is received "
			"but the handler has not been established.\n");
		return 0;
	}

	/* turn off the timer for calling isakmp_ph2expire() */ 
	SCHED_KILL(iph2->sce);

	iph2->status = PHASE2ST_EXPIRED;

	/* INITIATOR, begin phase 2 exchange. */
	/* allocate buffer for status management of pfkey message */
	if (iph2->side == INITIATOR) {

		initph2(iph2);

		/* update status for re-use */
		iph2->status = PHASE2ST_STATUS2;

		/* start isakmp initiation by using ident exchange */
		if (isakmp_post_acquire(iph2) < 0) {
			plog(LLV_ERROR, LOCATION, iph2->dst,
				"failed to begin ipsec sa "
				"re-negotication.\n");
			unbindph12(iph2);
			remph2(iph2);
			delph2(iph2);
			return -1;
		}

		return 0;
		/*NOTREACHED*/
	}

	/* If not received SADB_EXPIRE, INITIATOR delete ph2handle. */
	/* RESPONDER always delete ph2handle, keep silent.  RESPONDER doesn't
	 * manage IPsec SA, so delete the list */
	unbindph12(iph2);
	remph2(iph2);
	delph2(iph2);

	return 0;
}

static int
pk_recvacquire(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_x_policy *xpl;
	struct secpolicy *sp_out = NULL, *sp_in = NULL;
#define MAXNESTEDSA	5	/* XXX */
	struct ph2handle *iph2[MAXNESTEDSA];
	int n;	/* # of phase 2 handler */

	/* ignore this message because of local test mode. */
	if (f_local)
		return 0;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || mhp[SADB_X_EXT_POLICY] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb acquire message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	/* ignore if type is not IPSEC_POLICY_IPSEC */
	if (xpl->sadb_x_policy_type != IPSEC_POLICY_IPSEC) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"ignore SPDGET message. type is not IPsec.\n");
		return 0;
	}

	/* ignore it if src is multicast address */
    {
	struct sockaddr *sa = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

	if ((sa->sa_family == AF_INET
	  && IN_MULTICAST(ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr)))
#ifdef INET6
	 || (sa->sa_family == AF_INET6
	  && IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)sa)->sin6_addr))
#endif
	) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"ignore due to multicast address: %s.\n",
			saddrwop2str(sa));
		return 0;
	}
    }

	/*
	 * If there is a phase 2 handler against the policy identifier in
	 * the acquire message, and if
	 *    1. its state is less than PHASE2ST_ESTABLISHED, then racoon
	 *       should ignore such a acquire message becuase the phase 2
	 *       is just negotiating.
	 *    2. its state is equal to PHASE2ST_ESTABLISHED, then racoon
	 *       has to prcesss such a acquire message becuase racoon may
	 *       lost the expire message.
	 */
	iph2[0] = getph2byspid(xpl->sadb_x_policy_id);
	if (iph2[0] != NULL) {
		if (iph2[0]->status < PHASE2ST_ESTABLISHED) {
			plog(LLV_DEBUG, LOCATION, NULL,
				"ignore the acquire becuase ph2 found\n");
			return -1;
		}
		if (iph2[0]->status == PHASE2ST_EXPIRED)
			iph2[0] = NULL;
		/*FALLTHROUGH*/
	}

	/* search for proper policyindex */
	sp_out = getspbyspid(xpl->sadb_x_policy_id);
	if (sp_out == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "no policy found: id:%d.\n",
			xpl->sadb_x_policy_id);
		return -1;
	}
	plog(LLV_DEBUG, LOCATION, NULL,
		"suitable outbound SP found: %s.\n", spidx2str(&sp_out->spidx));

	/* get inbound policy */
    {
	struct policyindex spidx;

	spidx.dir = IPSEC_DIR_INBOUND;
	memcpy(&spidx.src, &sp_out->spidx.dst, sizeof(spidx.src));
	memcpy(&spidx.dst, &sp_out->spidx.src, sizeof(spidx.dst));
	spidx.prefs = sp_out->spidx.prefd;
	spidx.prefd = sp_out->spidx.prefs;
	spidx.ul_proto = sp_out->spidx.ul_proto;

	sp_in = getsp(&spidx);
	if (sp_in) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"suitable inbound SP found: %s.\n",
			spidx2str(&sp_in->spidx));
	} else {
		plog(LLV_NOTIFY, LOCATION, NULL,
			"no in-bound policy found: %s\n",
			spidx2str(&spidx));
	}
    }

	memset(iph2, 0, MAXNESTEDSA);

	n = 0;

	/* allocate a phase 2 */
	iph2[n] = newph2();
	if (iph2[n] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate phase2 entry.\n");
		return -1;
	}
	iph2[n]->side = INITIATOR;
	iph2[n]->spid = xpl->sadb_x_policy_id;
	iph2[n]->satype = msg->sadb_msg_satype;
	iph2[n]->seq = msg->sadb_msg_seq;
	iph2[n]->status = PHASE2ST_STATUS2;

	/* set end addresses of SA */
	iph2[n]->dst = dupsaddr(PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]));
	if (iph2[n]->dst == NULL) {
		delph2(iph2[n]);
		return -1;
	}
	iph2[n]->src = dupsaddr(PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]));
	if (iph2[n]->src == NULL) {
		delph2(iph2[n]);
		return -1;
	}

	plog(LLV_DEBUG, LOCATION, NULL,
		"new acquire %s\n", spidx2str(&sp_out->spidx));

	/* get sainfo */
    {
	vchar_t *idsrc, *iddst;

	idsrc = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.src,
				sp_out->spidx.prefs, sp_out->spidx.ul_proto);
	if (idsrc == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID for %s\n",
			spidx2str(&sp_out->spidx));
		delph2(iph2[n]);
		return -1;
	}
	iddst = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.dst,
				sp_out->spidx.prefd, sp_out->spidx.ul_proto);
	if (iddst == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID for %s\n",
			spidx2str(&sp_out->spidx));
		vfree(idsrc);
		delph2(iph2[n]);
		return -1;
	}
	iph2[n]->sainfo = getsainfo(idsrc, iddst);
	vfree(idsrc);
	vfree(iddst);
	if (iph2[n]->sainfo == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get sainfo.\n");
		delph2(iph2[n]);
		return -1;
		/* XXX should use the algorithm list from register message */
	}
    }

	if (set_proposal_from_policy(iph2[n], sp_out, sp_in) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to create saprop.\n");
		delph2(iph2[n]);
		return -1;
	}
	insph2(iph2[n]);

	/* start isakmp initiation by using ident exchange */
	/* XXX should be looped if there are multiple phase 2 handler. */
	if (isakmp_post_acquire(iph2[n]) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to begin ipsec sa negotication.\n");
		goto err;
	}

	return 0;

err:
	while (n >= 0) {
		unbindph12(iph2[n]);
		remph2(iph2[n]);
		delph2(iph2[n]);
		iph2[n] = NULL;
		n--;
	}
	return -1;
}

static int
pk_recvdelete(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_sa *sa;
	struct sockaddr *src, *dst;
	struct ph2handle *iph2 = NULL;
	u_int proto_id;

	/* ignore this message because of local test mode. */
	if (f_local)
		return 0;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_SA] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb acquire message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];
	sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
	src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
	dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

	/* the message has to be processed or not ? */
	if (msg->sadb_msg_pid == getpid()) {
		plog(LLV_DEBUG, LOCATION, NULL,
			"%s message is not interesting "
			"because the message was originated by me.\n",
			s_pfkey_type(msg->sadb_msg_type),
			msg->sadb_msg_pid);
		return -1;
	}

	proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
	if (proto_id == ~0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid proto_id %d\n", msg->sadb_msg_satype);
		return -1;
	}

	iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi);
	if (iph2 == NULL) {
		/* ignore */
		plog(LLV_ERROR, LOCATION, NULL,
			"no iph2 found: %s\n",
			sadbsecas2str(src, dst, msg->sadb_msg_satype,
				sa->sadb_sa_spi, IPSEC_MODE_ANY));
		return 0;
	}

	plog(LLV_ERROR, LOCATION, NULL,
		"pfkey DELETE received: %s\n",
		sadbsecas2str(iph2->src, iph2->dst,
			msg->sadb_msg_satype, sa->sadb_sa_spi, IPSEC_MODE_ANY));

	/* send delete information */
	if (iph2->status == PHASE2ST_ESTABLISHED)
		isakmp_info_send_d2(iph2);

	unbindph12(iph2);
	remph2(iph2);
	delph2(iph2);

	return 0;
}

static int
pk_recvflush(mhp)
	caddr_t *mhp;
{
	/* ignore this message because of local test mode. */
	if (f_local)
		return 0;

	/* sanity check */
	if (mhp[0] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb acquire message passed.\n");
		return -1;
	}

	flushph2();

	return 0;
}

static int
getsadbpolicy(policy0, policylen0, type, iph2)
	caddr_t *policy0;
	int *policylen0, type;
	struct ph2handle *iph2;
{
	struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
	struct sadb_x_policy *xpl;
	struct sadb_x_ipsecrequest *xisr;
	struct saproto *pr;
	caddr_t policy, p;
	int policylen;
	int xisrlen;
	u_int satype, mode;

	/* get policy buffer size */
	policylen = sizeof(struct sadb_x_policy);
	if (type != SADB_X_SPDDELETE) {
		for (pr = iph2->approval->head; pr; pr = pr->next) {
			xisrlen = sizeof(*xisr);
			if (pr->encmode == IPSECDOI_ATTR_ENC_MODE_TUNNEL) {
				xisrlen += (iph2->src->sa_len
				          + iph2->dst->sa_len);
			}

			policylen += PFKEY_ALIGN8(xisrlen);
		}
	}

	/* make policy structure */
	policy = racoon_malloc(policylen);
	if (!policy) {
		plog(LLV_ERROR, LOCATION, NULL,
			"buffer allocation failed.\n");
		return -1;
	}

	xpl = (struct sadb_x_policy *)policy;
	xpl->sadb_x_policy_len = PFKEY_UNIT64(policylen);
	xpl->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
	xpl->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
	xpl->sadb_x_policy_dir = spidx->dir;
	xpl->sadb_x_policy_id = 0;

	/* no need to append policy information any more if type is SPDDELETE */
	if (type == SADB_X_SPDDELETE)
		goto end;

	xisr = (struct sadb_x_ipsecrequest *)(xpl + 1);

	for (pr = iph2->approval->head; pr; pr = pr->next) {

		satype = doi2ipproto(pr->proto_id);
		if (satype == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proto_id %d\n", pr->proto_id);
			goto err;
		}
		mode = ipsecdoi2pfkey_mode(pr->encmode);
		if (mode == ~0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid encmode %d\n", pr->encmode);
			goto err;
		}

		/* 
		 * the policy level cannot be unique because the policy
		 * is defined later than SA, so req_id cannot be bound to SA.
		 */
		xisr->sadb_x_ipsecrequest_proto = satype;
		xisr->sadb_x_ipsecrequest_mode = mode;
		xisr->sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE;
		xisr->sadb_x_ipsecrequest_reqid = 0;
		p = (caddr_t)(xisr + 1);

		xisrlen = sizeof(*xisr);

		if (pr->encmode == IPSECDOI_ATTR_ENC_MODE_TUNNEL) {
			xisrlen += (iph2->src->sa_len + iph2->dst->sa_len);

			memcpy(p, iph2->src, iph2->src->sa_len);
			p += iph2->src->sa_len;

			memcpy(p, iph2->dst, iph2->dst->sa_len);
			p += iph2->dst->sa_len;
		}

		xisr->sadb_x_ipsecrequest_len = PFKEY_ALIGN8(xisrlen);
	}

end:
	*policy0 = policy;
	*policylen0 = policylen;

	return 0;

err:
	if (policy)
		racoon_free(policy);

	return -1;
}

int
pk_sendspdupdate2(iph2)
	struct ph2handle *iph2;
{
	struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
	caddr_t policy = NULL;
	int policylen = 0;
	u_int64_t ltime, vtime;

	ltime = iph2->approval->lifetime;
	vtime = 0;

	if (getsadbpolicy(&policy, &policylen, SADB_X_SPDUPDATE, iph2)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"getting sadb policy failed.\n");
		return -1;
	}

	if (pfkey_send_spdupdate2(
			lcconf->sock_pfkey,
			(struct sockaddr *)&spidx->src,
			spidx->prefs,
			(struct sockaddr *)&spidx->dst,
			spidx->prefd,
			spidx->ul_proto,
			ltime, vtime,
			policy, policylen, 0) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed send spdupdate2 (%s)\n",
			ipsec_strerror());
		goto end;
	}
	plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spdupdate2\n");

end:
	if (policy)
		racoon_free(policy);

	return 0;
}

static int
pk_recvspdupdate(mhp)
	caddr_t *mhp;
{
	/* sanity check */
	if (mhp[0] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spdupdate message passed.\n");
		return -1;
	}

	return 0;
}

/*
 * this function has to be used by responder side.
 */
int
pk_sendspdadd2(iph2)
	struct ph2handle *iph2;
{
	struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
	caddr_t policy = NULL;
	int policylen = 0;
	u_int64_t ltime, vtime;

	ltime = iph2->approval->lifetime;
	vtime = 0;

	if (getsadbpolicy(&policy, &policylen, SADB_X_SPDADD, iph2)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"getting sadb policy failed.\n");
		return -1;
	}

	if (pfkey_send_spdadd2(
			lcconf->sock_pfkey,
			(struct sockaddr *)&spidx->src,
			spidx->prefs,
			(struct sockaddr *)&spidx->dst,
			spidx->prefd,
			spidx->ul_proto,
			ltime, vtime,
			policy, policylen, 0) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed send spdadd2 (%s)\n",
			ipsec_strerror());
		goto end;
	}
	plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spdadd2\n");

end:
	if (policy)
		racoon_free(policy);

	return 0;
}

static int
pk_recvspdadd(mhp)
	caddr_t *mhp;
{
	struct sadb_address *saddr, *daddr;
	struct sadb_x_policy *xpl;
	struct policyindex spidx;
	struct secpolicy *sp;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || mhp[SADB_X_EXT_POLICY] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spdadd message passed.\n");
		return -1;
	}
	saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
	daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
			saddr + 1,
			daddr + 1,
			saddr->sadb_address_prefixlen,
			daddr->sadb_address_prefixlen,
			saddr->sadb_address_proto,
			&spidx);

	sp = getsp(&spidx);
	if (sp != NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"such policy already exists. "
			"anyway replace it: %s\n",
			spidx2str(&spidx));
		remsp(sp);
		delsp(sp);
	}

	if (addnewsp(mhp) < 0)
		return -1;

	return 0;
}

/*
 * this function has to be used by responder side.
 */
int
pk_sendspddelete(iph2)
	struct ph2handle *iph2;
{
	struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
	caddr_t policy = NULL;
	int policylen;

	if (getsadbpolicy(&policy, &policylen, SADB_X_SPDDELETE, iph2)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"getting sadb policy failed.\n");
		return -1;
	}

	if (pfkey_send_spddelete(
			lcconf->sock_pfkey,
			(struct sockaddr *)&spidx->src,
			spidx->prefs,
			(struct sockaddr *)&spidx->dst,
			spidx->prefd,
			spidx->ul_proto,
			policy, policylen, 0) < 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"libipsec failed send spddelete (%s)\n",
			ipsec_strerror());
		goto end;
	}
	plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spddelete\n");

end:
	if (policy)
		racoon_free(policy);

	return 0;
}

static int
pk_recvspddelete(mhp)
	caddr_t *mhp;
{
	struct sadb_address *saddr, *daddr;
	struct sadb_x_policy *xpl;
	struct policyindex spidx;
	struct secpolicy *sp;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || mhp[SADB_X_EXT_POLICY] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spddelete message passed.\n");
		return -1;
	}
	saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
	daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
			saddr + 1,
			daddr + 1,
			saddr->sadb_address_prefixlen,
			daddr->sadb_address_prefixlen,
			saddr->sadb_address_proto,
			&spidx);

	sp = getsp(&spidx);
	if (sp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no policy found: %s\n",
			spidx2str(&spidx));
		return -1;
	}

	remsp(sp);
	delsp(sp);

	return 0;
}

static int
pk_recvspdexpire(mhp)
	caddr_t *mhp;
{
	struct sadb_address *saddr, *daddr;
	struct sadb_x_policy *xpl;
	struct policyindex spidx;
	struct secpolicy *sp;

	/* sanity check */
	if (mhp[0] == NULL
	 || mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || mhp[SADB_X_EXT_POLICY] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spdexpire message passed.\n");
		return -1;
	}
	saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
	daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
			saddr + 1,
			daddr + 1,
			saddr->sadb_address_prefixlen,
			daddr->sadb_address_prefixlen,
			saddr->sadb_address_proto,
			&spidx);

	sp = getsp(&spidx);
	if (sp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no policy found: %s\n",
			spidx2str(&spidx));
		return -1;
	}

	remsp(sp);
	delsp(sp);

	return 0;
}

static int
pk_recvspdget(mhp)
	caddr_t *mhp;
{
	/* sanity check */
	if (mhp[0] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spdget message passed.\n");
		return -1;
	}

	return 0;
}

static int
pk_recvspddump(mhp)
	caddr_t *mhp;
{
	struct sadb_msg *msg;
	struct sadb_address *saddr, *daddr;
	struct sadb_x_policy *xpl;
	struct policyindex spidx;
	struct secpolicy *sp;

	/* sanity check */
	if (mhp[0] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spddump message passed.\n");
		return -1;
	}
	msg = (struct sadb_msg *)mhp[0];

	saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
	daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
			saddr + 1,
			daddr + 1,
			saddr->sadb_address_prefixlen,
			daddr->sadb_address_prefixlen,
			saddr->sadb_address_proto,
			&spidx);

	sp = getsp(&spidx);
	if (sp != NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"such policy already exists. "
			"anyway replace it: %s\n",
			spidx2str(&spidx));
		remsp(sp);
		delsp(sp);
	}

	if (addnewsp(mhp) < 0)
		return -1;

	return 0;
}

static int
pk_recvspdflush(mhp)
	caddr_t *mhp;
{
	/* sanity check */
	if (mhp[0] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spdflush message passed.\n");
		return -1;
	}

	flushsp();

	return 0;
}

/*
 * send error against acquire message to kenrel.
 */
int
pk_sendeacquire(iph2)
	struct ph2handle *iph2;
{
	struct sadb_msg *newmsg;
	int len;

	len = sizeof(struct sadb_msg);
	newmsg = racoon_calloc(1, len);
	if (newmsg == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get buffer to send acquire.\n");
		return -1;
	}

	memset(newmsg, 0, len);
	newmsg->sadb_msg_version = PF_KEY_V2;
	newmsg->sadb_msg_type = SADB_ACQUIRE;
	newmsg->sadb_msg_errno = ENOENT;	/* XXX */
	newmsg->sadb_msg_satype = iph2->satype;
	newmsg->sadb_msg_len = PFKEY_UNIT64(len);
	newmsg->sadb_msg_reserved = 0;
	newmsg->sadb_msg_seq = iph2->seq;
	newmsg->sadb_msg_pid = (u_int32_t)getpid();

	/* send message */
	len = pfkey_send(lcconf->sock_pfkey, newmsg, len);

	racoon_free(newmsg);

	return 0;
}

/*
 * check if the algorithm is supported or not.
 * OUT	 0: ok
 *	-1: ng
 */
int
pk_checkalg(class, calg, keylen)
	int class, calg, keylen;
{
	int sup, error;
	u_int alg;
	struct sadb_alg alg0;

	switch (algclass2doi(class)) {
	case IPSECDOI_PROTO_IPSEC_ESP:
		sup = SADB_EXT_SUPPORTED_ENCRYPT;
		break;
	case IPSECDOI_ATTR_AUTH:
		sup = SADB_EXT_SUPPORTED_AUTH;
		break;
	case IPSECDOI_PROTO_IPCOMP:
		plog(LLV_DEBUG, LOCATION, NULL,
			"compression algorithm can not be checked "
			"because sadb message doesn't support it.\n");
		return 0;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid algorithm class.\n");
		return -1;
	}
	alg = ipsecdoi2pfkey_alg(algclass2doi(class), algtype2doi(class, calg));
	if (alg == ~0)
		return -1;

	if (keylen == 0) {
		if (ipsec_get_keylen(sup, alg, &alg0)) {
			plog(LLV_ERROR, LOCATION, NULL,
				"%s.\n", ipsec_strerror());
			return -1;
		}
		keylen = alg0.sadb_alg_minbits;
	}

	error = ipsec_check_keylen(sup, alg, keylen);
	if (error)
		plog(LLV_ERROR, LOCATION, NULL,
			"%s.\n", ipsec_strerror());

	return error;
}

/*
 * differences with pfkey_recv() in libipsec/pfkey.c:
 * - never performs busy wait loop.
 * - returns NULL and set *lenp to negative on fatal failures
 * - returns NULL and set *lenp to non-negative on non-fatal failures
 * - returns non-NULL on success
 */
static struct sadb_msg *
pk_recv(so, lenp)
	int so;
	int *lenp;
{
	struct sadb_msg buf, *newmsg;
	int reallen;

	*lenp = recv(so, (caddr_t)&buf, sizeof(buf), MSG_PEEK);
	if (*lenp < 0)
		return NULL;	/*fatal*/
	else if (*lenp < sizeof(buf))
		return NULL;

	reallen = PFKEY_UNUNIT64(buf.sadb_msg_len);
	if ((newmsg = racoon_calloc(1, reallen)) == NULL)
		return NULL;

	*lenp = recv(so, (caddr_t)newmsg, reallen, MSG_PEEK);
	if (*lenp < 0) {
		racoon_free(newmsg);
		return NULL;	/*fatal*/
	} else if (*lenp != reallen) {
		racoon_free(newmsg);
		return NULL;
	}

	*lenp = recv(so, (caddr_t)newmsg, reallen, 0);
	if (*lenp < 0) {
		racoon_free(newmsg);
		return NULL;	/*fatal*/
	} else if (*lenp != reallen) {
		racoon_free(newmsg);
		return NULL;
	}

	return newmsg;
}

/* see handler.h */
u_int32_t
pk_getseq()
{
	return (u_int32_t)random();
}

static int
addnewsp(mhp)
	caddr_t *mhp;
{
	struct secpolicy *new;
	struct sadb_address *saddr, *daddr;
	struct sadb_x_policy *xpl;

	/* sanity check */
	if (mhp[SADB_EXT_ADDRESS_SRC] == NULL
	 || mhp[SADB_EXT_ADDRESS_DST] == NULL
	 || mhp[SADB_X_EXT_POLICY] == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"inappropriate sadb spd management message passed.\n");
		return -1;
	}

	saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
	daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
	xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

	new = newsp();
	if (new == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate buffer\n");
		return -1;
	}

	new->spidx.dir = xpl->sadb_x_policy_dir;
	new->id = xpl->sadb_x_policy_id;
	new->policy = xpl->sadb_x_policy_type;
	new->req = NULL;

	/* check policy */
	switch (xpl->sadb_x_policy_type) {
	case IPSEC_POLICY_DISCARD:
	case IPSEC_POLICY_NONE:
	case IPSEC_POLICY_ENTRUST:
	case IPSEC_POLICY_BYPASS:
		break;

	case IPSEC_POLICY_IPSEC:
	    {
		int tlen;
		struct sadb_x_ipsecrequest *xisr;
		struct ipsecrequest **p_isr = &new->req;

		/* validity check */
		if (PFKEY_EXTLEN(xpl) < sizeof(*xpl)) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid msg length.\n");
			return -1;
		}

		tlen = PFKEY_EXTLEN(xpl) - sizeof(*xpl);
		xisr = (struct sadb_x_ipsecrequest *)(xpl + 1);

		while (tlen > 0) {

			/* length check */
			if (xisr->sadb_x_ipsecrequest_len < sizeof(*xisr)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid msg length.\n");
				return -1;
			}

			/* allocate request buffer */
			*p_isr = newipsecreq();
			if (*p_isr == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to get new ipsecreq.\n");
				return -1;
			}

			/* set values */
			(*p_isr)->next = NULL;

			switch (xisr->sadb_x_ipsecrequest_proto) {
			case IPPROTO_ESP:
			case IPPROTO_AH:
			case IPPROTO_IPCOMP:
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid proto type: %u\n",
					xisr->sadb_x_ipsecrequest_proto);
				return -1;
			}
			(*p_isr)->saidx.proto = xisr->sadb_x_ipsecrequest_proto;

			switch (xisr->sadb_x_ipsecrequest_mode) {
			case IPSEC_MODE_TRANSPORT:
			case IPSEC_MODE_TUNNEL:
				break;
			case IPSEC_MODE_ANY:
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid mode: %u\n",
					xisr->sadb_x_ipsecrequest_mode);
				return -1;
			}
			(*p_isr)->saidx.mode = xisr->sadb_x_ipsecrequest_mode;

			switch (xisr->sadb_x_ipsecrequest_level) {
			case IPSEC_LEVEL_DEFAULT:
			case IPSEC_LEVEL_USE:
			case IPSEC_LEVEL_REQUIRE:
				break;
			case IPSEC_LEVEL_UNIQUE:
				(*p_isr)->saidx.reqid =
					xisr->sadb_x_ipsecrequest_reqid;
				break;

			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid level: %u\n",
					xisr->sadb_x_ipsecrequest_level);
				return -1;
			}
			(*p_isr)->level = xisr->sadb_x_ipsecrequest_level;

			/* set IP addresses if there */
			if (xisr->sadb_x_ipsecrequest_len > sizeof(*xisr)) {
				struct sockaddr *paddr;

				paddr = (struct sockaddr *)(xisr + 1);
				bcopy(paddr, &(*p_isr)->saidx.src,
					paddr->sa_len);

				paddr = (struct sockaddr *)((caddr_t)paddr
							+ paddr->sa_len);
				bcopy(paddr, &(*p_isr)->saidx.dst,
					paddr->sa_len);
			}

			(*p_isr)->sp = new;

			/* initialization for the next. */
			p_isr = &(*p_isr)->next;
			tlen -= xisr->sadb_x_ipsecrequest_len;

			/* validity check */
			if (tlen < 0) {
				plog(LLV_ERROR, LOCATION, NULL,
					"becoming tlen < 0\n");
			}

			xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr
			                 + xisr->sadb_x_ipsecrequest_len);
		}
	    }
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid policy type.\n");
		return -1;
	}

	KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
			saddr + 1,
			daddr + 1,
			saddr->sadb_address_prefixlen,
			daddr->sadb_address_prefixlen,
			saddr->sadb_address_proto,
			&new->spidx);

	inssp(new);

	return 0;
}

/* proto/mode/src->dst spi */
const char *
sadbsecas2str(src, dst, proto, spi, mode)
	struct sockaddr *src, *dst;
	int proto;
	u_int32_t spi;
	int mode;
{
	static char buf[256];
	u_int doi_proto, doi_mode = 0;
	char *p;
	int blen, i;

	doi_proto = pfkey2ipsecdoi_proto(proto);
	if (doi_proto == ~0)
		return NULL;
	if (mode) {
		doi_mode = pfkey2ipsecdoi_mode(mode);
		if (doi_mode == ~0)
			return NULL;
	}

	blen = sizeof(buf) - 1;
	p = buf;

	i = snprintf(p, blen, "%s%s%s ",
		s_ipsecdoi_proto(doi_proto),
		mode ? "/" : "",
		mode ? s_ipsecdoi_encmode(doi_mode) : "");
	if (i < 0 || i >= blen)
		return NULL;
	p += i;
	blen -= i;

	i = snprintf(p, blen, "%s->", saddrwop2str(src));
	if (i < 0 || i >= blen)
		return NULL;
	p += i;
	blen -= i;

	i = snprintf(p, blen, "%s ", saddrwop2str(dst));
	if (i < 0 || i >= blen)
		return NULL;
	p += i;
	blen -= i;

	if (spi) {
		snprintf(p, blen, "spi=%lu(0x%lx)", (unsigned long)ntohl(spi),
		    (unsigned long)ntohl(spi));
	}

	return buf;
}
