/*	$NetBSD: ipsec_doi.c,v 1.52 2020/05/14 08:34:19 msaitoh Exp $	*/

/* Id: ipsec_doi.c,v 1.55 2006/08/17 09:20:41 vanhu 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 <netinet/in.h>

#include PATH_IPSEC_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.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 "var.h"
#include "vmbuf.h"
#include "misc.h"
#include "plog.h"
#include "debug.h"

#include "cfparse_proto.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "ipsec_doi.h"
#include "oakley.h"
#include "remoteconf.h"
#include "localconf.h"
#include "sockmisc.h"
#include "handler.h"
#include "policy.h"
#include "algorithm.h"
#include "sainfo.h"
#include "proposal.h"
#include "crypto_openssl.h"
#include "strnames.h"
#include "gcmalloc.h"

#ifdef ENABLE_NATT
#include "nattraversal.h"
#endif

#ifdef HAVE_GSSAPI
#include <iconv.h>
#include "gssapi.h"
#ifdef HAVE_ICONV_2ND_CONST
#define __iconv_const const
#else
#define __iconv_const
#endif
#endif

static vchar_t *get_ph1approval __P((struct ph1handle *, u_int32_t, u_int32_t,
				     struct prop_pair **));
static int get_ph1approvalx __P((struct remoteconf *, void *));

static int t2isakmpsa __P((struct isakmp_pl_t *, struct isakmpsa *, u_int32_t));
static int cmp_aproppair_i __P((struct prop_pair *, struct prop_pair *));
static struct prop_pair *get_ph2approval __P((struct ph2handle *,
	struct prop_pair **));
static struct prop_pair *get_ph2approvalx __P((struct ph2handle *,
	struct prop_pair *));
static void free_proppair0 __P((struct prop_pair *));
static struct prop_pair ** get_proppair_and_doi_sit __P((vchar_t *, int,
							 u_int32_t *, u_int32_t *));

static int get_transform
	__P((struct isakmp_pl_p *, struct prop_pair **, int *));
static u_int32_t ipsecdoi_set_ld __P((vchar_t *));

static int check_doi __P((u_int32_t));
static int check_situation __P((u_int32_t));

static int check_prot_main __P((int));
static int check_prot_quick __P((int));
static int (*check_protocol[]) __P((int)) = {
	check_prot_main,	/* IPSECDOI_TYPE_PH1 */
	check_prot_quick,	/* IPSECDOI_TYPE_PH2 */
};

static int check_spi_size __P((int, int));

static int check_trns_isakmp __P((int));
static int check_trns_ah __P((int));
static int check_trns_esp __P((int));
static int check_trns_ipcomp __P((int));
static int (*check_transform[]) __P((int)) = {
	0,
	check_trns_isakmp,	/* IPSECDOI_PROTO_ISAKMP */
	check_trns_ah,		/* IPSECDOI_PROTO_IPSEC_AH */
	check_trns_esp,		/* IPSECDOI_PROTO_IPSEC_ESP */
	check_trns_ipcomp,	/* IPSECDOI_PROTO_IPCOMP */
};

static int check_attr_isakmp __P((struct isakmp_pl_t *));
static int check_attr_ah __P((struct isakmp_pl_t *));
static int check_attr_esp __P((struct isakmp_pl_t *));
static int check_attr_ipsec __P((int, struct isakmp_pl_t *));
static int check_attr_ipcomp __P((struct isakmp_pl_t *));
static int (*check_attributes[]) __P((struct isakmp_pl_t *)) = {
	0,
	check_attr_isakmp,	/* IPSECDOI_PROTO_ISAKMP */
	check_attr_ah,		/* IPSECDOI_PROTO_IPSEC_AH */
	check_attr_esp,		/* IPSECDOI_PROTO_IPSEC_ESP */
	check_attr_ipcomp,	/* IPSECDOI_PROTO_IPCOMP */
};

static int setph1prop __P((struct isakmpsa *, caddr_t));
static int setph1trns __P((struct isakmpsa *, caddr_t));
static int setph1attr __P((struct isakmpsa *, caddr_t));
static vchar_t *setph2proposal0 __P((const struct ph2handle *,
	const struct saprop *, const struct saproto *));

struct ph1approvalx_ctx {
	struct prop_pair *p;
	struct isakmpsa *sa;
};

/*%%%*/
/*
 * check phase 1 SA payload.
 * make new SA payload to be replyed not including general header.
 * the pointer to one of isakmpsa in proposal is set into iph1->approval.
 * OUT:
 *	positive: the pointer to new buffer of SA payload.
 *		  network byte order.
 *	NULL	: error occurd.
 */
int
ipsecdoi_checkph1proposal(sa, iph1)
	vchar_t *sa;
	struct ph1handle *iph1;
{
	vchar_t *newsa;		/* new SA payload approved. */
	struct prop_pair **pair;
	u_int32_t doitype, sittype;

	/* get proposal pair */
	pair = get_proppair_and_doi_sit(sa, IPSECDOI_TYPE_PH1,
					&doitype, &sittype);
	if (pair == NULL)
		return -1;

	/* check and get one SA for use */
	newsa = get_ph1approval(iph1, doitype, sittype, pair);
	free_proppair(pair);

	if (newsa == NULL)
		return -1;

	iph1->sa_ret = newsa;
	return 0;
}

static void
print_ph1proposal(struct prop_pair *pair, struct isakmpsa *s)
{
	struct isakmp_pl_p *prop = pair->prop;
	struct isakmp_pl_t *trns = pair->trns;

	plog(LLV_DEBUG, LOCATION, NULL,
	     "prop#=%d, prot-id=%s, spi-size=%d, #trns=%d\n",
	     prop->p_no, s_ipsecdoi_proto(prop->proto_id),
	     prop->spi_size, prop->num_t);
	plog(LLV_DEBUG, LOCATION, NULL,
	     "trns#=%d, trns-id=%s\n",
	     trns->t_no, s_ipsecdoi_trns(prop->proto_id, trns->t_id));
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  lifetime = %ld\n", (long) s->lifetime);
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  lifebyte = %zu\n", s->lifebyte);
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  enctype = %s\n",
	     s_oakley_attr_v(OAKLEY_ATTR_ENC_ALG, s->enctype));
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  encklen = %d\n", s->encklen);
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  hashtype = %s\n",
	     s_oakley_attr_v(OAKLEY_ATTR_HASH_ALG, s->hashtype));
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  authmethod = %s\n",
	     s_oakley_attr_v(OAKLEY_ATTR_AUTH_METHOD, s->authmethod));
	plog(LLV_DEBUG, LOCATION, NULL,
	     "  dh_group = %s\n",
	     s_oakley_attr_v(OAKLEY_ATTR_GRP_DESC, s->dh_group));
}


/*
 * acceptable check for remote configuration.
 * return a new SA payload to be reply to peer.
 */

static vchar_t *
get_ph1approval(iph1, doitype, sittype, pair)
	struct ph1handle *iph1;
	u_int32_t doitype, sittype;
	struct prop_pair **pair;
{
	vchar_t *newsa;
	struct ph1approvalx_ctx ctx;
	struct prop_pair *s, *p;
	struct rmconfselector rmsel;
	struct isakmpsa *sa;
	int i;

	memset(&rmsel, 0, sizeof(rmsel));
	rmsel.remote = iph1->remote;

	if (iph1->approval) {
		delisakmpsa(iph1->approval);
		iph1->approval = NULL;
	}

	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		if (pair[i] == NULL)
			continue;
		for (s = pair[i]; s; s = s->next) {
			/* compare proposal and select one */
			for (p = s; p; p = p->tnext) {
				sa = newisakmpsa();
				ctx.p = p;
				ctx.sa = sa;
				if (t2isakmpsa(p->trns, sa,
					       iph1->vendorid_mask) < 0)
					continue;
				print_ph1proposal(p, sa);
				if (iph1->rmconf != NULL) {
					if (get_ph1approvalx(iph1->rmconf, &ctx))
						goto found;
				} else {
					if (enumrmconf(&rmsel, get_ph1approvalx, &ctx))
						goto found;
				}
				delisakmpsa(sa);
			}
		}
	}

	plog(LLV_ERROR, LOCATION, NULL, "no suitable proposal found.\n");

	return NULL;

found:
	sa = ctx.sa;
	plog(LLV_DEBUG, LOCATION, NULL, "an acceptable proposal found.\n");

	/* check DH group settings */
	if (sa->dhgrp) {
		if (sa->dhgrp->prime && sa->dhgrp->gen1) {
			/* it's ok */
			goto saok;
		}
		plog(LLV_WARNING, LOCATION, NULL,
			"invalid DH parameter found, use default.\n");
		oakley_dhgrp_free(sa->dhgrp);
		sa->dhgrp=NULL;
	}

	if (oakley_setdhgroup(sa->dh_group, &sa->dhgrp) == -1) {
		sa->dhgrp = NULL;
		delisakmpsa(sa);
		return NULL;
	}

saok:
#ifdef HAVE_GSSAPI
	if (sa->gssid != NULL)
		plog(LLV_DEBUG, LOCATION, NULL, "gss id in new sa '%.*s'\n",
		    (int)sa->gssid->l, sa->gssid->v);
	if (iph1->side == INITIATOR) {
		if (iph1->rmconf->proposal->gssid != NULL)
			iph1->gi_i = vdup(iph1->rmconf->proposal->gssid);
		if (sa->gssid != NULL)
			iph1->gi_r = vdup(sa->gssid);
	} else {
		if (sa->gssid != NULL) {
			iph1->gi_r = vdup(sa->gssid);
			iph1->gi_i = gssapi_get_id(iph1);
		}
	}
	if (iph1->gi_i != NULL)
		plog(LLV_DEBUG, LOCATION, NULL, "GIi is %.*s\n",
		    (int)iph1->gi_i->l, iph1->gi_i->v);
	if (iph1->gi_r != NULL)
		plog(LLV_DEBUG, LOCATION, NULL, "GIr is %.*s\n",
		    (int)iph1->gi_r->l, iph1->gi_r->v);
#endif
	plog(LLV_DEBUG, LOCATION, NULL, "agreed on %s auth.\n",
	     s_oakley_attr_method(sa->authmethod));

	newsa = get_sabyproppair(doitype, sittype, p);
	if (newsa == NULL)
		delisakmpsa(sa);
	else
		iph1->approval = sa;

	return newsa;
}

/*
 * compare peer's single proposal and all of my proposal.
 * and select one if suiatable.
 */
static int
get_ph1approvalx(rmconf, ctx)
	struct remoteconf *rmconf;
	void *ctx;
{
	struct ph1approvalx_ctx *pctx = (struct ph1approvalx_ctx *) ctx;
	struct isakmpsa *sa;

	/* do the hard work */
	sa = checkisakmpsa(rmconf->pcheck_level, pctx->sa, rmconf->proposal);
	if (sa == NULL)
		return 0;

	/* duplicate and modify the found SA to match proposal */
	sa = dupisakmpsa(sa);

	switch (rmconf->pcheck_level) {
	case PROP_CHECK_OBEY:
		sa->lifetime = pctx->sa->lifetime;
		sa->lifebyte = pctx->sa->lifebyte;
		sa->encklen = pctx->sa->encklen;
		break;
	case PROP_CHECK_CLAIM:
	case PROP_CHECK_STRICT:
		if (pctx->sa->lifetime < sa->lifetime)
			sa->lifetime = pctx->sa->lifetime;
		if (pctx->sa->lifebyte < sa->lifebyte)
			sa->lifebyte = pctx->sa->lifebyte;
		if (pctx->sa->encklen > sa->encklen)
			sa->encklen = pctx->sa->encklen;
		break;
	default:
		break;
	}

	/* replace the proposal with our approval sa */
	delisakmpsa(pctx->sa);
	pctx->sa = sa;

	return 1;
}

/*
 * get ISAKMP data attributes
 */
static int
t2isakmpsa(trns, sa, vendorid_mask)
	struct isakmp_pl_t *trns;
	struct isakmpsa *sa;
	u_int32_t vendorid_mask;
{
	struct isakmp_data *d, *prev;
	int flag, type;
	int error = -1;
	int life_t;
	int keylen = 0;
	vchar_t *val = NULL;
	int len, tlen;
	u_char *p;

	tlen = ntohs(trns->h.len) - sizeof(*trns);
	prev = (struct isakmp_data *)NULL;
	d = (struct isakmp_data *)(trns + 1);

	/* default */
	life_t = OAKLEY_ATTR_SA_LD_TYPE_DEFAULT;
	sa->lifetime = OAKLEY_ATTR_SA_LD_SEC_DEFAULT;
	sa->lifebyte = 0;
	sa->dhgrp = racoon_calloc(1, sizeof(struct dhgroup));
	if (!sa->dhgrp)
		goto err;

	while (tlen > 0) {

		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%s, flag=0x%04x, lorv=%s\n",
			s_oakley_attr(type), flag,
			s_oakley_attr_v(type, ntohs(d->lorv)));

		/* get variable-sized item */
		switch (type) {
		case OAKLEY_ATTR_GRP_PI:
		case OAKLEY_ATTR_GRP_GEN_ONE:
		case OAKLEY_ATTR_GRP_GEN_TWO:
		case OAKLEY_ATTR_GRP_CURVE_A:
		case OAKLEY_ATTR_GRP_CURVE_B:
		case OAKLEY_ATTR_SA_LD:
		case OAKLEY_ATTR_GRP_ORDER:
			if (flag) {	/*TV*/
				len = 2;
				p = (u_char *)&d->lorv;
			} else {	/*TLV*/
				len = ntohs(d->lorv);
				p = (u_char *)(d + 1);
			}
			val = vmalloc(len);
			if (!val)
				return -1;
			memcpy(val->v, p, len);
			break;

		default:
			break;
		}

		switch (type) {
		case OAKLEY_ATTR_ENC_ALG:
			sa->enctype = (u_int16_t)ntohs(d->lorv);
			break;

		case OAKLEY_ATTR_HASH_ALG:
			sa->hashtype = (u_int16_t)ntohs(d->lorv);
			break;

		case OAKLEY_ATTR_AUTH_METHOD:
			sa->authmethod = ntohs(d->lorv);
#ifdef HAVE_GSSAPI
			if (sa->authmethod == OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB_REAL &&
			    (vendorid_mask & VENDORID_GSSAPI_MASK))
				sa->authmethod = OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB;
#endif
			break;

		case OAKLEY_ATTR_GRP_DESC:
			sa->dh_group = (u_int16_t)ntohs(d->lorv);
			break;

		case OAKLEY_ATTR_GRP_TYPE:
		{
			int type = (int)ntohs(d->lorv);
			if (type == OAKLEY_ATTR_GRP_TYPE_MODP)
				sa->dhgrp->type = type;
			else
				return -1;
			break;
		}
		case OAKLEY_ATTR_GRP_PI:
			sa->dhgrp->prime = val;
			break;

		case OAKLEY_ATTR_GRP_GEN_ONE:
			vfree(val);
			if (!flag)
				sa->dhgrp->gen1 = ntohs(d->lorv);
			else {
				int len = ntohs(d->lorv);
				sa->dhgrp->gen1 = 0;
				if (len > 4)
					return -1;
				memcpy(&sa->dhgrp->gen1, d + 1, len);
				sa->dhgrp->gen1 = ntohl(sa->dhgrp->gen1);
			}
			break;

		case OAKLEY_ATTR_GRP_GEN_TWO:
			vfree(val);
			if (!flag)
				sa->dhgrp->gen2 = ntohs(d->lorv);
			else {
				int len = ntohs(d->lorv);
				sa->dhgrp->gen2 = 0;
				if (len > 4)
					return -1;
				memcpy(&sa->dhgrp->gen2, d + 1, len);
				sa->dhgrp->gen2 = ntohl(sa->dhgrp->gen2);
			}
			break;

		case OAKLEY_ATTR_GRP_CURVE_A:
			sa->dhgrp->curve_a = val;
			break;

		case OAKLEY_ATTR_GRP_CURVE_B:
			sa->dhgrp->curve_b = val;
			break;

		case OAKLEY_ATTR_SA_LD_TYPE:
		{
			int type = (int)ntohs(d->lorv);
			switch (type) {
			case OAKLEY_ATTR_SA_LD_TYPE_SEC:
			case OAKLEY_ATTR_SA_LD_TYPE_KB:
				life_t = type;
				break;
			default:
				life_t = OAKLEY_ATTR_SA_LD_TYPE_DEFAULT;
				break;
			}
			break;
		}
		case OAKLEY_ATTR_SA_LD:
			if (!prev
			 || (ntohs(prev->type) & ~ISAKMP_GEN_MASK) !=
					OAKLEY_ATTR_SA_LD_TYPE) {
				plog(LLV_ERROR, LOCATION, NULL,
				    "life duration must follow ltype\n");
				break;
			}

			switch (life_t) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
				sa->lifetime = ipsecdoi_set_ld(val);
				vfree(val);
				if (sa->lifetime == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid life duration.\n");
					goto err;
				}
				break;
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				sa->lifebyte = ipsecdoi_set_ld(val);
				vfree(val);
				if (sa->lifebyte == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid life duration.\n");
					goto err;
				}
				break;
			default:
				vfree(val);
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid life type: %d\n", life_t);
				goto err;
			}
			break;

		case OAKLEY_ATTR_KEY_LEN:
		{
			int len = ntohs(d->lorv);
			if (len % 8 != 0) {
				plog(LLV_ERROR, LOCATION, NULL,
					"keylen %d: not multiple of 8\n",
					len);
				goto err;
			}
			sa->encklen = (u_int16_t)len;
			keylen++;
			break;
		}
		case OAKLEY_ATTR_PRF:
		case OAKLEY_ATTR_FIELD_SIZE:
			/* unsupported */
			break;

		case OAKLEY_ATTR_GRP_ORDER:
			sa->dhgrp->order = val;
			break;
#ifdef HAVE_GSSAPI
		case OAKLEY_ATTR_GSS_ID:
		{
			int error = -1;
			iconv_t cd = (iconv_t) -1;
			size_t srcleft, dstleft, rv;
			__iconv_const char *src;
			char *dst;
			int len = ntohs(d->lorv);

			/*
			 * Older verions of racoon just placed the
			 * ISO-Latin-1 string on the wire directly.
			 * Check to see if we are configured to be
			 * compatible with this behavior.
			 */
			if (lcconf->gss_id_enc == LC_GSSENC_LATIN1) {
				if ((sa->gssid = vmalloc(len)) == NULL) {
					plog(LLV_ERROR, LOCATION, NULL,
					    "failed to allocate memory\n");
					goto out;
				}
				memcpy(sa->gssid->v, d + 1, len);
				plog(LLV_DEBUG, LOCATION, NULL,
				    "received old-style gss "
				    "id '%.*s' (len %zu)\n",
				    (int)sa->gssid->l, sa->gssid->v,
				    sa->gssid->l);
				error = 0;
				goto out;
			}

			/*
			 * For Windows 2000 compatibility, we expect
			 * the GSS ID attribute on the wire to be
			 * encoded in UTF-16LE.  Internally, we work
			 * in ISO-Latin-1.  Therefore, we should need
			 * 1/2 the specified length, which should always
			 * be a multiple of 2 octets.
			 */
			cd = iconv_open("latin1", "utf-16le");
			if (cd == (iconv_t) -1) {
				plog(LLV_ERROR, LOCATION, NULL,
				    "unable to initialize utf-16le -> latin1 "
				    "conversion descriptor: %s\n",
				    strerror(errno));
				goto out;
			}

			if ((sa->gssid = vmalloc(len / 2)) == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
				    "failed to allocate memory\n");
				goto out;
			}

			src = (__iconv_const char *)(d + 1);
			srcleft = len;

			dst = sa->gssid->v;
			dstleft = len / 2;

			rv = iconv(cd, (__iconv_const char **)&src, &srcleft,
				   &dst, &dstleft);
			if (rv != 0) {
				if (rv == -1) {
					plog(LLV_ERROR, LOCATION, NULL,
					    "unable to convert GSS ID from "
					    "utf-16le -> latin1: %s\n",
					    strerror(errno));
				} else {
					plog(LLV_ERROR, LOCATION, NULL,
					    "%zd character%s in GSS ID cannot "
					    "be represented in latin1\n",
					    rv, rv == 1 ? "" : "s");
				}
				goto out;
			}

			/* XXX dstleft should always be 0; assert it? */
			sa->gssid->l = (len / 2) - dstleft;

			plog(LLV_DEBUG, LOCATION, NULL,
			    "received gss id '%.*s' (len %zu)\n",
			    (int)sa->gssid->l, sa->gssid->v, sa->gssid->l);

			error = 0;
out:
			if (cd != (iconv_t)-1)
				(void)iconv_close(cd);

			if ((error != 0) && (sa->gssid != NULL)) {
				vfree(sa->gssid);
				sa->gssid = NULL;
			}
			break;
		}
#endif /* HAVE_GSSAPI */

		default:
			break;
		}

		prev = d;
		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d + sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + ntohs(d->lorv));
			d = (struct isakmp_data *)((char *)d + sizeof(*d) + ntohs(d->lorv));
		}
	}

	/* key length must not be specified on some algorithms */
	if (keylen) {
		if (sa->enctype == OAKLEY_ATTR_ENC_ALG_DES
#ifdef HAVE_OPENSSL_IDEA_H
		 || sa->enctype == OAKLEY_ATTR_ENC_ALG_IDEA
#endif
		 || sa->enctype == OAKLEY_ATTR_ENC_ALG_3DES) {
			plog(LLV_ERROR, LOCATION, NULL,
				"keylen must not be specified "
				"for encryption algorithm %d\n",
				sa->enctype);
			return -1;
		}
	}

	return 0;
err:
	return error;
}

/*%%%*/
/*
 * check phase 2 SA payload and select single proposal.
 * make new SA payload to be replyed not including general header.
 * This function is called by responder only.
 * OUT:
 *	0: succeed.
 *	-1: error occured.
 */
int
ipsecdoi_selectph2proposal(iph2)
	struct ph2handle *iph2;
{
	struct prop_pair **pair;
	struct prop_pair *ret;
	u_int32_t doitype, sittype;

	/* get proposal pair */
	pair = get_proppair_and_doi_sit(iph2->sa, IPSECDOI_TYPE_PH2,
					&doitype, &sittype);
	if (pair == NULL)
		return -1;

	/* check and select a proposal. */
	ret = get_ph2approval(iph2, pair);
	free_proppair(pair);
	if (ret == NULL)
		return -1;

	/* make a SA to be replayed. */
	/* SPI must be updated later. */
	iph2->sa_ret = get_sabyproppair(doitype, sittype, ret);
	free_proppair0(ret);
	if (iph2->sa_ret == NULL)
		return -1;

	return 0;
}

/*
 * check phase 2 SA payload returned from responder.
 * This function is called by initiator only.
 * OUT:
 *	0: valid.
 *	-1: invalid.
 */
int
ipsecdoi_checkph2proposal(iph2)
	struct ph2handle *iph2;
{
	struct prop_pair **rpair = NULL, **spair = NULL;
	struct prop_pair *p;
	int i, n, num;
	int error = -1;
	vchar_t *sa_ret = NULL;
	u_int32_t doitype, sittype;

	/* get proposal pair of SA sent. */
	spair = get_proppair_and_doi_sit(iph2->sa, IPSECDOI_TYPE_PH2,
					 &doitype, &sittype);
	if (spair == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get prop pair.\n");
		goto end;
	}

	/* XXX should check the number of transform */

	/* get proposal pair of SA replayed */
	rpair = get_proppair(iph2->sa_ret, IPSECDOI_TYPE_PH2);
	if (rpair == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get prop pair.\n");
		goto end;
	}

	/* check proposal is only one ? */
	n = 0;
	num = 0;
	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		if (rpair[i]) {
			n = i;
			num++;
		}
	}
	if (num == 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no proposal received.\n");
		goto end;
	}
	if (num != 1) {
		plog(LLV_ERROR, LOCATION, NULL,
			"some proposals received.\n");
		goto end;
	}

	if (spair[n] == NULL) {
		plog(LLV_WARNING, LOCATION, NULL,
			"invalid proposal number:%d received.\n", i);
	}


	if (rpair[n]->tnext != NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"multi transforms replyed.\n");
		goto end;
	}

	if (cmp_aproppair_i(rpair[n], spair[n])) {
		plog(LLV_ERROR, LOCATION, NULL,
			"proposal mismathed.\n");
		goto end;
	}

	/*
	 * check and select a proposal.
	 * ensure that there is no modification of the proposal by
	 * cmp_aproppair_i()
	 */
	p = get_ph2approval(iph2, rpair);
	if (p == NULL)
		goto end;

	/* make a SA to be replayed. */
	sa_ret = iph2->sa_ret;
	iph2->sa_ret = get_sabyproppair(doitype, sittype, p);
	free_proppair0(p);
	if (iph2->sa_ret == NULL)
		goto end;

	error = 0;

end:
	if (rpair)
		free_proppair(rpair);
	if (spair)
		free_proppair(spair);
	if (sa_ret)
		vfree(sa_ret);

	return error;
}

/*
 * compare two prop_pair which is assumed to have same proposal number.
 * the case of bundle or single SA, NOT multi transforms.
 * a: a proposal that is multi protocols and single transform, usually replyed.
 * b: a proposal that is multi protocols and multi transform, usually sent.
 * NOTE: this function is for initiator.
 * OUT
 *	0: equal
 *	1: not equal
 * XXX cannot understand the comment!
 */
static int
cmp_aproppair_i(a, b)
	struct prop_pair *a, *b;
{
	struct prop_pair *p, *q, *r;
	int len;

	for (p = a, q = b; p && q; p = p->next, q = q->next) {
		for (r = q; r; r = r->tnext) {
			/* compare trns */
			if (p->trns->t_no == r->trns->t_no)
				break;
		}
		if (!r) {
			/* no suitable transform found */
			plog(LLV_ERROR, LOCATION, NULL,
				"no suitable transform found.\n");
			return -1;
		}

		/* compare prop */
		if (p->prop->p_no != r->prop->p_no) {
			plog(LLV_WARNING, LOCATION, NULL,
				"proposal #%d mismatched, "
				"expected #%d.\n",
				r->prop->p_no, p->prop->p_no);
			/*FALLTHROUGH*/
		}

		if (p->prop->proto_id != r->prop->proto_id) {
			plog(LLV_ERROR, LOCATION, NULL,
				"proto_id mismathed: my:%d peer:%d\n",
				r->prop->proto_id, p->prop->proto_id);
			return -1;
		}

		if (p->prop->spi_size != r->prop->spi_size) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid spi size: %d.\n",
				p->prop->spi_size);
			return -1;
		}

		/* check #of transforms */
		if (p->prop->num_t != 1) {
			plog(LLV_WARNING, LOCATION, NULL,
				"#of transform is %d, "
				"but expected 1.\n", p->prop->num_t);
			/*FALLTHROUGH*/
		}

		if (p->trns->t_id != r->trns->t_id) {
			plog(LLV_WARNING, LOCATION, NULL,
				"transform number has been modified.\n");
			/*FALLTHROUGH*/
		}
		if (p->trns->reserved != r->trns->reserved) {
			plog(LLV_WARNING, LOCATION, NULL,
				"reserved field should be zero.\n");
			/*FALLTHROUGH*/
		}

		/* compare attribute */
		len = ntohs(r->trns->h.len) - sizeof(*p->trns);
		if (memcmp(p->trns + 1, r->trns + 1, len) != 0) {
			plog(LLV_WARNING, LOCATION, NULL,
				"attribute has been modified.\n");
			/*FALLTHROUGH*/
		}
	}
	if ((p && !q) || (!p && q)) {
		/* # of protocols mismatched */
		plog(LLV_ERROR, LOCATION, NULL,
			"#of protocols mismatched.\n");
		return -1;
	}

	return 0;
}

/*
 * acceptable check for policy configuration.
 * return a new SA payload to be reply to peer.
 */
static struct prop_pair *
get_ph2approval(iph2, pair)
	struct ph2handle *iph2;
	struct prop_pair **pair;
{
	struct prop_pair *ret;
	int i;

	iph2->approval = NULL;

	plog(LLV_DEBUG, LOCATION, NULL,
		"begin compare proposals.\n");

	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		if (pair[i] == NULL)
			continue;
		plog(LLV_DEBUG, LOCATION, NULL,
			"pair[%d]: %p\n", i, pair[i]);
		print_proppair(LLV_DEBUG, pair[i]);

		/* compare proposal and select one */
		ret = get_ph2approvalx(iph2, pair[i]);
		if (ret != NULL) {
			/* found */
			return ret;
		}
	}

	plog(LLV_ERROR, LOCATION, NULL, "no suitable policy found.\n");

	return NULL;
}

/*
 * compare my proposal and peers just one proposal.
 * set a approval.
 */
static struct prop_pair *
get_ph2approvalx(iph2, pp)
	struct ph2handle *iph2;
	struct prop_pair *pp;
{
	struct prop_pair *ret = NULL;
	struct saprop *pr0, *pr = NULL;
	struct saprop *q1, *q2;

	pr0 = aproppair2saprop(pp);
	if (pr0 == NULL)
		return NULL;

	for (q1 = pr0; q1; q1 = q1->next) {
		for (q2 = iph2->proposal; q2; q2 = q2->next) {
			plog(LLV_DEBUG, LOCATION, NULL,
				"peer's single bundle:\n");
			printsaprop0(LLV_DEBUG, q1);
			plog(LLV_DEBUG, LOCATION, NULL,
				"my single bundle:\n");
			printsaprop0(LLV_DEBUG, q2);

			pr = cmpsaprop_alloc(iph2->ph1, q1, q2, iph2->side);
			if (pr != NULL)
				goto found;

			plog(LLV_ERROR, LOCATION, NULL,
				"not matched\n");
		}
	}
	/* no proposal matching */
err:
	flushsaprop(pr0);
	return NULL;

found:
	flushsaprop(pr0);
	plog(LLV_DEBUG, LOCATION, NULL, "matched\n");
	iph2->approval = pr;

    {
	struct saproto *sp;
	struct prop_pair *p, *x;
	struct prop_pair *n = NULL;

	ret = NULL;

	for (p = pp; p; p = p->next) {
		/*
		 * find a proposal with matching proto_id.
		 * we have analyzed validity already, in cmpsaprop_alloc().
		 */
		for (sp = pr->head; sp; sp = sp->next) {
			if (sp->proto_id == p->prop->proto_id)
				break;
		}
		if (!sp)
			goto err;
		if (sp->head->next)
			goto err;	/* XXX */

		for (x = p; x; x = x->tnext)
			if (sp->head->trns_no == x->trns->t_no)
				break;
		if (!x)
			goto err;	/* XXX */

		n = racoon_calloc(1, sizeof(struct prop_pair));
		if (n == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to get buffer.\n");
			goto err;
		}

		n->prop = x->prop;
		n->trns = x->trns;

		/* need to preserve the order */
		for (x = ret; x && x->next; x = x->next)
			;
		if (x && x->prop == n->prop) {
			for (/*nothing*/; x && x->tnext; x = x->tnext)
				;
			x->tnext = n;
		} else {
			if (x)
				x->next = n;
			else {
				ret = n;
			}
		}

		/* #of transforms should be updated ? */
	}
    }

	return ret;
}

void
free_proppair(pair)
	struct prop_pair **pair;
{
	int i;

	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		free_proppair0(pair[i]);
		pair[i] = NULL;
	}
	racoon_free(pair);
}

static void
free_proppair0(pair)
	struct prop_pair *pair;
{
	struct prop_pair *p, *q, *r, *s;

	p = pair;
	while (p) {
		q = p->next;
		r = p;
		while (r) {
			s = r->tnext;
			racoon_free(r);
			r = s;
		}
		p = q;
	}
}

/*
 * get proposal pairs from SA payload.
 * tiny check for proposal payload.
 */
static struct prop_pair **
get_proppair_and_doi_sit(sa, mode, doitype, sittype)
	vchar_t *sa;
	int mode;
	u_int32_t *doitype, *sittype;
{
	struct prop_pair **pair = NULL;
	int num_p = 0;			/* number of proposal for use */
	int tlen;
	caddr_t bp;
	int i;
	struct ipsecdoi_sa_b *sab = (struct ipsecdoi_sa_b *)sa->v;

	plog(LLV_DEBUG, LOCATION, NULL, "total SA len=%zu\n", sa->l);
	plogdump(LLV_DEBUG, sa->v, sa->l);

	/* check SA payload size */
	if (sa->l < sizeof(*sab)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"Invalid SA length = %zu.\n", sa->l);
		goto bad;
	}

	/* check DOI */
	if (check_doi(ntohl(sab->doi)) < 0)
		goto bad;
	if (doitype != NULL)
		*doitype = ntohl(sab->doi);

	/* check SITUATION */
	if (check_situation(ntohl(sab->sit)) < 0)
		goto bad;
	if (sittype != NULL)
		*sittype = ntohl(sab->sit);

	pair = racoon_calloc(1, MAXPROPPAIRLEN * sizeof(*pair));
	if (pair == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get buffer.\n");
		goto bad;
	}

	bp = (caddr_t)(sab + 1);
	tlen = sa->l - sizeof(*sab);

    {
	struct isakmp_pl_p *prop;
	int proplen;
	vchar_t *pbuf = NULL;
	struct isakmp_parse_t *pa;

	pbuf = isakmp_parsewoh(ISAKMP_NPTYPE_P, (struct isakmp_gen *)bp, tlen);
	if (pbuf == NULL)
		goto bad;

	for (pa = (struct isakmp_parse_t *)pbuf->v;
	     pa->type != ISAKMP_NPTYPE_NONE;
	     pa++) {
		/* check the value of next payload */
		if (pa->type != ISAKMP_NPTYPE_P) {
			plog(LLV_ERROR, LOCATION, NULL,
				"Invalid payload type=%u\n", pa->type);
			vfree(pbuf);
			goto bad;
		}

		prop = (struct isakmp_pl_p *)pa->ptr;
		proplen = pa->len;

		plog(LLV_DEBUG, LOCATION, NULL,
			"proposal #%u len=%d\n", prop->p_no, proplen);

		if (proplen == 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid proposal with length %d\n", proplen);
			vfree(pbuf);
			goto bad;
		}

		/* check Protocol ID */
		if (!check_protocol[mode]) {
			plog(LLV_ERROR, LOCATION, NULL,
				"unsupported mode %d\n", mode);
			continue;
		}

		if (check_protocol[mode](prop->proto_id) < 0)
			continue;

		/* check SPI length when IKE. */
		if (check_spi_size(prop->proto_id, prop->spi_size) < 0)
			continue;

		/* get transform */
		if (get_transform(prop, pair, &num_p) < 0) {
			vfree(pbuf);
			goto bad;
		}
	}
	vfree(pbuf);
	pbuf = NULL;
    }

    {
	int notrans, nprop;
	struct prop_pair *p, *q;

	/* check for proposals with no transforms */
	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		if (!pair[i])
			continue;

		plog(LLV_DEBUG, LOCATION, NULL, "pair %d:\n", i);
		print_proppair(LLV_DEBUG, pair[i]);

		notrans = nprop = 0;
		for (p = pair[i]; p; p = p->next) {
			if (p->trns == NULL) {
				notrans++;
				break;
			}
			for (q = p; q; q = q->tnext)
				nprop++;
		}

#if 0
		/*
		 * XXX at this moment, we cannot accept proposal group
		 * with multiple proposals.  this should be fixed.
		 */
		if (pair[i]->next) {
			plog(LLV_WARNING, LOCATION, NULL,
				"proposal #%u ignored "
				"(multiple proposal not supported)\n",
				pair[i]->prop->p_no);
			notrans++;
		}
#endif

		if (notrans) {
			for (p = pair[i]; p; p = q) {
				q = p->next;
				racoon_free(p);
			}
			pair[i] = NULL;
			num_p--;
		} else {
			plog(LLV_DEBUG, LOCATION, NULL,
				"proposal #%u: %d transform\n",
				pair[i]->prop->p_no, nprop);
		}
	}
    }

	/* bark if no proposal is found. */
	if (num_p <= 0) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no Proposal found.\n");
		goto bad;
	}

	return pair;
bad:
	if (pair != NULL)
		racoon_free(pair);
	return NULL;
}

struct prop_pair **
get_proppair(sa, mode)
	vchar_t *sa;
	int mode;
{
	return get_proppair_and_doi_sit(sa, mode, NULL, NULL);
}


/*
 * check transform payload.
 * OUT:
 *	positive: return the pointer to the payload of valid transform.
 *	0	: No valid transform found.
 */
static int
get_transform(prop, pair, num_p)
	struct isakmp_pl_p *prop;
	struct prop_pair **pair;
	int *num_p;
{
	int tlen; /* total length of all transform in a proposal */
	caddr_t bp;
	struct isakmp_pl_t *trns;
	int trnslen;
	vchar_t *pbuf = NULL;
	struct isakmp_parse_t *pa;
	struct prop_pair *p = NULL, *q;
	int num_t;

	bp = (caddr_t)prop + sizeof(struct isakmp_pl_p) + prop->spi_size;
	tlen = ntohs(prop->h.len)
		- (sizeof(struct isakmp_pl_p) + prop->spi_size);
	pbuf = isakmp_parsewoh(ISAKMP_NPTYPE_T, (struct isakmp_gen *)bp, tlen);
	if (pbuf == NULL)
		return -1;

	/* check and get transform for use */
	num_t = 0;
	for (pa = (struct isakmp_parse_t *)pbuf->v;
	     pa->type != ISAKMP_NPTYPE_NONE;
	     pa++) {

		num_t++;

		/* check the value of next payload */
		if (pa->type != ISAKMP_NPTYPE_T) {
			plog(LLV_ERROR, LOCATION, NULL,
				"Invalid payload type=%u\n", pa->type);
			break;
		}

		trns = (struct isakmp_pl_t *)pa->ptr;
		trnslen = pa->len;

		plog(LLV_DEBUG, LOCATION, NULL,
			"transform #%u len=%u\n", trns->t_no, trnslen);

		/* check transform ID */
		if (prop->proto_id >= ARRAYLEN(check_transform)) {
			plog(LLV_WARNING, LOCATION, NULL,
				"unsupported proto_id %u\n",
				prop->proto_id);
			continue;
		}
		if (prop->proto_id >= ARRAYLEN(check_attributes)) {
			plog(LLV_WARNING, LOCATION, NULL,
				"unsupported proto_id %u\n",
				prop->proto_id);
			continue;
		}

		if (!check_transform[prop->proto_id]
		 || !check_attributes[prop->proto_id]) {
			plog(LLV_WARNING, LOCATION, NULL,
				"unsupported proto_id %u\n",
				prop->proto_id);
			continue;
		}
		if (check_transform[prop->proto_id](trns->t_id) < 0)
			continue;

		/* check data attributes */
		if (check_attributes[prop->proto_id](trns) != 0)
			continue;

		p = racoon_calloc(1, sizeof(*p));
		if (p == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to get buffer.\n");
			vfree(pbuf);
			return -1;
		}
		p->prop = prop;
		p->trns = trns;

		/* need to preserve the order */
		for (q = pair[prop->p_no]; q && q->next; q = q->next)
			;
		if (q && q->prop == p->prop) {
			for (/*nothing*/; q && q->tnext; q = q->tnext)
				;
			q->tnext = p;
		} else {
			if (q)
				q->next = p;
			else {
				pair[prop->p_no] = p;
				(*num_p)++;
			}
		}
	}

	vfree(pbuf);

	return 0;
}

/*
 * make a new SA payload from prop_pair.
 * NOTE: this function make spi value clear.
 */
vchar_t *
get_sabyproppair(doitype, sittype, pair)
	u_int32_t doitype, sittype;
	struct prop_pair *pair;
{
	vchar_t *newsa;
	int newtlen;
	u_int8_t *np_p = NULL;
	struct prop_pair *p;
	int prophlen, trnslen;
	caddr_t bp;

	newtlen = sizeof(struct ipsecdoi_sa_b);
	for (p = pair; p; p = p->next) {
		newtlen += sizeof(struct isakmp_pl_p);
		newtlen += p->prop->spi_size;
		newtlen += ntohs(p->trns->h.len);
	}

	newsa = vmalloc(newtlen);
	if (newsa == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "failed to get newsa.\n");
		return NULL;
	}
	bp = newsa->v;

	((struct isakmp_gen *)bp)->len = htons(newtlen);

	/* update some of values in SA header */
	((struct ipsecdoi_sa_b *)bp)->doi = htonl(doitype);
	((struct ipsecdoi_sa_b *)bp)->sit = htonl(sittype);
	bp += sizeof(struct ipsecdoi_sa_b);

	/* create proposal payloads */
	for (p = pair; p; p = p->next) {
		prophlen = sizeof(struct isakmp_pl_p)
				+ p->prop->spi_size;
		trnslen = ntohs(p->trns->h.len);

		if (np_p)
			*np_p = ISAKMP_NPTYPE_P;

		/* create proposal */

		memcpy(bp, p->prop, prophlen);
		((struct isakmp_pl_p *)bp)->h.np = ISAKMP_NPTYPE_NONE;
		((struct isakmp_pl_p *)bp)->h.len = htons(prophlen + trnslen);
		((struct isakmp_pl_p *)bp)->num_t = 1;
		np_p = &((struct isakmp_pl_p *)bp)->h.np;
		memset(bp + sizeof(struct isakmp_pl_p), 0, p->prop->spi_size);
		bp += prophlen;

		/* create transform */
		memcpy(bp, p->trns, trnslen);
		((struct isakmp_pl_t *)bp)->h.np = ISAKMP_NPTYPE_NONE;
		((struct isakmp_pl_t *)bp)->h.len = htons(trnslen);
		bp += trnslen;
	}

	return newsa;
}

/*
 * update responder's spi
 */
int
ipsecdoi_updatespi(iph2)
	struct ph2handle *iph2;
{
	struct prop_pair **pair, *p;
	struct saprop *pp;
	struct saproto *pr;
	int i;
	int error = -1;
	u_int8_t *spi;

	pair = get_proppair(iph2->sa_ret, IPSECDOI_TYPE_PH2);
	if (pair == NULL)
		return -1;
	for (i = 0; i < MAXPROPPAIRLEN; i++) {
		if (pair[i])
			break;
	}
	if (i == MAXPROPPAIRLEN || pair[i]->tnext) {
		/* multiple transform must be filtered by selectph2proposal.*/
		goto end;
	}

	pp = iph2->approval;

	/* create proposal payloads */
	for (p = pair[i]; p; p = p->next) {
		/*
		 * find a proposal/transform with matching proto_id/t_id.
		 * we have analyzed validity already, in cmpsaprop_alloc().
		 */
		for (pr = pp->head; pr; pr = pr->next) {
			if (p->prop->proto_id == pr->proto_id &&
			    p->trns->t_id == pr->head->trns_id) {
				break;
			}
		}
		if (!pr)
			goto end;

		/*
		 * XXX SPI bits are left-filled, for use with IPComp.
		 * we should be switching to variable-length spi field...
		 */
		spi = (u_int8_t *)&pr->spi;
		spi += sizeof(pr->spi);
		spi -= pr->spisize;
		memcpy((caddr_t)p->prop + sizeof(*p->prop), spi, pr->spisize);
	}

	error = 0;
end:
	free_proppair(pair);
	return error;
}

/*
 * make a new SA payload from prop_pair.
 */
vchar_t *
get_sabysaprop(pp0, sa0)
	struct saprop *pp0;
	vchar_t *sa0;
{
	struct prop_pair **pair = NULL;
	vchar_t *newsa = NULL;
	int newtlen;
	u_int8_t *np_p = NULL;
	struct prop_pair *p = NULL;
	struct saprop *pp;
	struct saproto *pr;
	struct satrns *tr;
	int prophlen, trnslen;
	caddr_t bp;
	int error = -1;

	/* get proposal pair */
	pair = get_proppair(sa0, IPSECDOI_TYPE_PH2);
	if (pair == NULL)
		goto out;

	newtlen = sizeof(struct ipsecdoi_sa_b);
	for (pp = pp0; pp; pp = pp->next) {

		if (pair[pp->prop_no] == NULL)
			goto out;

		for (pr = pp->head; pr; pr = pr->next) {
			newtlen += (sizeof(struct isakmp_pl_p)
				+ pr->spisize);

			for (tr = pr->head; tr; tr = tr->next) {
				for (p = pair[pp->prop_no]; p; p = p->tnext) {
					if (tr->trns_no == p->trns->t_no)
						break;
				}
				if (p == NULL)
					goto out;

				newtlen += ntohs(p->trns->h.len);
			}
		}
	}

	newsa = vmalloc(newtlen);
	if (newsa == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "failed to get newsa.\n");
		goto out;
	}
	bp = newsa->v;

	/* some of values of SA must be updated in the out of this function */
	((struct isakmp_gen *)bp)->len = htons(newtlen);
	bp += sizeof(struct ipsecdoi_sa_b);

	/* create proposal payloads */
	for (pp = pp0; pp; pp = pp->next) {

		for (pr = pp->head; pr; pr = pr->next) {
			prophlen = sizeof(struct isakmp_pl_p)
					+ p->prop->spi_size;

			for (tr = pr->head; tr; tr = tr->next) {
				for (p = pair[pp->prop_no]; p; p = p->tnext) {
					if (tr->trns_no == p->trns->t_no)
						break;
				}
				if (p == NULL)
					goto out;

				trnslen = ntohs(p->trns->h.len);

				if (np_p)
					*np_p = ISAKMP_NPTYPE_P;

				/* create proposal */

				memcpy(bp, p->prop, prophlen);
				((struct isakmp_pl_p *)bp)->h.np = ISAKMP_NPTYPE_NONE;
				((struct isakmp_pl_p *)bp)->h.len = htons(prophlen + trnslen);
				((struct isakmp_pl_p *)bp)->num_t = 1;
				np_p = &((struct isakmp_pl_p *)bp)->h.np;
				bp += prophlen;

				/* create transform */
				memcpy(bp, p->trns, trnslen);
				((struct isakmp_pl_t *)bp)->h.np = ISAKMP_NPTYPE_NONE;
				((struct isakmp_pl_t *)bp)->h.len = htons(trnslen);
				bp += trnslen;
			}
		}
	}

	error = 0;
out:
	if (pair != NULL)
		racoon_free(pair);

	if (error != 0) {
		if (newsa != NULL) {
			vfree(newsa);
			newsa = NULL;
		}
	}

	return newsa;
}

/*
 * If some error happens then return 0.  Although 0 means that lifetime is zero,
 * such a value should not be accepted.
 * Also 0 of lifebyte should not be included in a packet although 0 means not
 * to care of it.
 */
static u_int32_t
ipsecdoi_set_ld(buf)
	vchar_t *buf;
{
	u_int32_t ld;

	if (buf == 0)
		return 0;

	switch (buf->l) {
	case 2:
		ld = ntohs(*(u_int16_t *)buf->v);
		break;
	case 4:
		ld = ntohl(*(u_int32_t *)buf->v);
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"length %zu of life duration "
			"isn't supported.\n", buf->l);
		return 0;
	}

	return ld;
}

/*
 * parse responder-lifetime attributes from payload
 */
int
ipsecdoi_parse_responder_lifetime(notify, lifetime_sec, lifetime_kb)
	struct isakmp_pl_n *notify;
	u_int32_t *lifetime_sec;
	u_int32_t *lifetime_kb;
{
	struct isakmp_data *d;
	int flag, type, tlen, ld_type = -1;
	u_int16_t lorv;
	u_int32_t value;

	tlen = ntohs(notify->h.len) - sizeof(*notify) - notify->spi_size;
        d = (struct isakmp_data *)((char *)(notify + 1) +
		notify->spi_size);

	while (tlen >= sizeof(struct isakmp_data)) {
		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;
		lorv = ntohs(d->lorv);

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%s, flag=0x%04x, lorv=%s\n",
			s_ipsecdoi_attr(type), flag,
			s_ipsecdoi_attr_v(type, lorv));

		switch (type) {
		case IPSECDOI_ATTR_SA_LD_TYPE:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when LD_TYPE.\n");
				return -1;
			}
			ld_type = lorv;
			break;
		case IPSECDOI_ATTR_SA_LD:
			if (flag)
				value = lorv;
			else if (lorv == 2)
				value = ntohs(*(u_int16_t *)(d + 1));
			else if (lorv == 4)
				value = ntohl(*(u_int32_t *)(d + 1));
			else {
				plog(LLV_ERROR, LOCATION, NULL,
					"payload length %d for lifetime "
					"data length is unsupported.\n", lorv);
				return -1;
			}

			switch (ld_type) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
				if (lifetime_sec != NULL)
					*lifetime_sec = value;
				plog(LLV_INFO, LOCATION, NULL,
					"received RESPONDER-LIFETIME: %d "
					"seconds\n", value);
				break;
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				if (lifetime_kb != NULL)
					*lifetime_kb = value;
				plog(LLV_INFO, LOCATION, NULL,
					"received RESPONDER-LIFETIME: %d "
					"kbytes\n", value);
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"lifetime data received without "
					"lifetime data type.\n");
				return -1;
			}
			break;
		}

		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + lorv);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d) + lorv);
		}
	}

	return 0;
}


/*%%%*/
/*
 * check DOI
 */
static int
check_doi(doi)
	u_int32_t doi;
{
	switch (doi) {
	case IPSEC_DOI:
		return 0;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid value of DOI 0x%08x.\n", doi);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check situation
 */
static int
check_situation(sit)
	u_int32_t sit;
{
	switch (sit) {
	case IPSECDOI_SIT_IDENTITY_ONLY:
		return 0;

	case IPSECDOI_SIT_SECRECY:
	case IPSECDOI_SIT_INTEGRITY:
		plog(LLV_ERROR, LOCATION, NULL,
			"situation 0x%08x unsupported yet.\n", sit);
		return -1;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid situation 0x%08x.\n", sit);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check protocol id in main mode
 */
static int
check_prot_main(proto_id)
	int proto_id;
{
	switch (proto_id) {
	case IPSECDOI_PROTO_ISAKMP:
		return 0;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"Illegal protocol id=%u.\n", proto_id);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check protocol id in quick mode
 */
static int
check_prot_quick(proto_id)
	int proto_id;
{
	switch (proto_id) {
	case IPSECDOI_PROTO_IPSEC_AH:
	case IPSECDOI_PROTO_IPSEC_ESP:
		return 0;

	case IPSECDOI_PROTO_IPCOMP:
		return 0;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid protocol id %d.\n", proto_id);
		return -1;
	}
	/* NOT REACHED */
}

static int
check_spi_size(proto_id, size)
	int proto_id, size;
{
	switch (proto_id) {
	case IPSECDOI_PROTO_ISAKMP:
		if (size != 0) {
			/* WARNING */
			plog(LLV_WARNING, LOCATION, NULL,
				"SPI size isn't zero, but IKE proposal.\n");
		}
		return 0;

	case IPSECDOI_PROTO_IPSEC_AH:
	case IPSECDOI_PROTO_IPSEC_ESP:
		if (size != 4) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid SPI size=%d for IPSEC proposal.\n",
				size);
			return -1;
		}
		return 0;

	case IPSECDOI_PROTO_IPCOMP:
		if (size != 2 && size != 4) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid SPI size=%d for IPCOMP proposal.\n",
				size);
			return -1;
		}
		return 0;

	default:
		/* ??? */
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check transform ID in ISAKMP.
 */
static int
check_trns_isakmp(t_id)
	int t_id;
{
	switch (t_id) {
	case IPSECDOI_KEY_IKE:
		return 0;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid transform-id=%u in proto_id=%u.\n",
			t_id, IPSECDOI_KEY_IKE);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check transform ID in AH.
 */
static int
check_trns_ah(t_id)
	int t_id;
{
	switch (t_id) {
	case IPSECDOI_AH_MD5:
	case IPSECDOI_AH_SHA:
	case IPSECDOI_AH_SHA256:
	case IPSECDOI_AH_SHA384:
	case IPSECDOI_AH_SHA512:
		return 0;
	case IPSECDOI_AH_DES:
		plog(LLV_ERROR, LOCATION, NULL,
			"not support transform-id=%u in AH.\n", t_id);
		return -1;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid transform-id=%u in AH.\n", t_id);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check transform ID in ESP.
 */
static int
check_trns_esp(t_id)
	int t_id;
{
	switch (t_id) {
	case IPSECDOI_ESP_DES:
	case IPSECDOI_ESP_3DES:
	case IPSECDOI_ESP_NULL:
	case IPSECDOI_ESP_RC5:
	case IPSECDOI_ESP_CAST:
	case IPSECDOI_ESP_BLOWFISH:
	case IPSECDOI_ESP_AES:
	case IPSECDOI_ESP_AESGCM16:
	case IPSECDOI_ESP_TWOFISH:
	case IPSECDOI_ESP_CAMELLIA:
		return 0;
	case IPSECDOI_ESP_DES_IV32:
	case IPSECDOI_ESP_DES_IV64:
	case IPSECDOI_ESP_IDEA:
	case IPSECDOI_ESP_3IDEA:
	case IPSECDOI_ESP_RC4:
		plog(LLV_ERROR, LOCATION, NULL,
			"not support transform-id=%u in ESP.\n", t_id);
		return -1;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid transform-id=%u in ESP.\n", t_id);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check transform ID in IPCOMP.
 */
static int
check_trns_ipcomp(t_id)
	int t_id;
{
	switch (t_id) {
	case IPSECDOI_IPCOMP_OUI:
	case IPSECDOI_IPCOMP_DEFLATE:
	case IPSECDOI_IPCOMP_LZS:
		return 0;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid transform-id=%u in IPCOMP.\n", t_id);
		return -1;
	}
	/* NOT REACHED */
}

/*
 * check data attributes in IKE.
 */
static int
check_attr_isakmp(trns)
	struct isakmp_pl_t *trns;
{
	struct isakmp_data *d;
	int tlen;
	int flag, type;
	u_int16_t lorv;

	tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
	d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));

	while (tlen > 0) {
		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;
		lorv = ntohs(d->lorv);

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%s, flag=0x%04x, lorv=%s\n",
			s_oakley_attr(type), flag,
			s_oakley_attr_v(type, lorv));

		/*
		 * some of the attributes must be encoded in TV.
		 * see RFC2409 Appendix A "Attribute Classes".
		 */
		switch (type) {
		case OAKLEY_ATTR_ENC_ALG:
		case OAKLEY_ATTR_HASH_ALG:
		case OAKLEY_ATTR_AUTH_METHOD:
		case OAKLEY_ATTR_GRP_DESC:
		case OAKLEY_ATTR_GRP_TYPE:
		case OAKLEY_ATTR_SA_LD_TYPE:
		case OAKLEY_ATTR_PRF:
		case OAKLEY_ATTR_KEY_LEN:
		case OAKLEY_ATTR_FIELD_SIZE:
			if (!flag) {	/* TLV*/
				plog(LLV_ERROR, LOCATION, NULL,
					"oakley attribute %d must be TV.\n",
					type);
				return -1;
			}
			break;
		}

		/* sanity check for TLV.  length must be specified. */
		if (!flag && lorv == 0) {	/*TLV*/
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid length %d for TLV attribute %d.\n",
				lorv, type);
			return -1;
		}

		switch (type) {
		case OAKLEY_ATTR_ENC_ALG:
			if (!alg_oakley_encdef_ok(lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalied encryption algorithm=%d.\n",
					lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_HASH_ALG:
			if (!alg_oakley_hashdef_ok(lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalied hash algorithm=%d.\n",
					lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_AUTH_METHOD:
			switch (lorv) {
			case OAKLEY_ATTR_AUTH_METHOD_PSKEY:
			case OAKLEY_ATTR_AUTH_METHOD_RSASIG:
#ifdef ENABLE_HYBRID
			case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_I:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_I:
#endif
#if defined(ENABLE_HYBRID) || defined(HAVE_GSSAPI)
				/* These two authentication method IDs overlap. */
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_I:
			/*case OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB:*/
#endif
				break;
			case OAKLEY_ATTR_AUTH_METHOD_DSSSIG:
#ifdef ENABLE_HYBRID
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R:
			case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R:
			case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_I:
			case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_I:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_I:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_I:
			case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R:
#endif
			case OAKLEY_ATTR_AUTH_METHOD_RSAENC:
			case OAKLEY_ATTR_AUTH_METHOD_RSAREV:
				plog(LLV_ERROR, LOCATION, NULL,
					"auth method %s isn't supported.\n",
					s_oakley_attr_method(lorv));
				return -1;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid auth method %d.\n",
					lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_GRP_DESC:
			if (!alg_oakley_dhdef_ok(lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid DH group %d.\n",
					lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_GRP_TYPE:
			switch (lorv) {
			case OAKLEY_ATTR_GRP_TYPE_MODP:
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"unsupported DH group type %d.\n",
					lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_GRP_PI:
		case OAKLEY_ATTR_GRP_GEN_ONE:
			/* sanity checks? */
			break;

		case OAKLEY_ATTR_GRP_GEN_TWO:
		case OAKLEY_ATTR_GRP_CURVE_A:
		case OAKLEY_ATTR_GRP_CURVE_B:
			plog(LLV_ERROR, LOCATION, NULL,
				"attr type=%u isn't supported.\n", type);
			return -1;

		case OAKLEY_ATTR_SA_LD_TYPE:
			switch (lorv) {
			case OAKLEY_ATTR_SA_LD_TYPE_SEC:
			case OAKLEY_ATTR_SA_LD_TYPE_KB:
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid life type %d.\n", lorv);
				return -1;
			}
			break;

		case OAKLEY_ATTR_SA_LD:
			/* should check the value */
			break;

		case OAKLEY_ATTR_PRF:
		case OAKLEY_ATTR_KEY_LEN:
			break;

		case OAKLEY_ATTR_FIELD_SIZE:
			plog(LLV_ERROR, LOCATION, NULL,
				"attr type=%u isn't supported.\n", type);
			return -1;

		case OAKLEY_ATTR_GRP_ORDER:
			break;

		case OAKLEY_ATTR_GSS_ID:
			break;

		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid attribute type %d.\n", type);
			return -1;
		}

		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + lorv);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d) + lorv);
		}
	}

	return 0;
}

/*
 * check data attributes in IPSEC AH/ESP.
 */
static int
check_attr_ah(trns)
	struct isakmp_pl_t *trns;
{
	return check_attr_ipsec(IPSECDOI_PROTO_IPSEC_AH, trns);
}

static int
check_attr_esp(trns)
	struct isakmp_pl_t *trns;
{
	return check_attr_ipsec(IPSECDOI_PROTO_IPSEC_ESP, trns);
}

static int
check_attr_ipsec(proto_id, trns)
	int proto_id;
	struct isakmp_pl_t *trns;
{
	struct isakmp_data *d;
	int tlen;
	int flag, type = 0;
	u_int16_t lorv;
	int attrseen[16];	/* XXX magic number */

	tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
	d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));
	memset(attrseen, 0, sizeof(attrseen));

	while (tlen > 0) {
		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;
		lorv = ntohs(d->lorv);

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%s, flag=0x%04x, lorv=%s\n",
			s_ipsecdoi_attr(type), flag,
			s_ipsecdoi_attr_v(type, lorv));

		if (type < sizeof(attrseen)/sizeof(attrseen[0]))
			attrseen[type]++;

		switch (type) {
		case IPSECDOI_ATTR_ENC_MODE:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when ENC_MODE.\n");
				return -1;
			}

			switch (lorv) {
			case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
			case IPSECDOI_ATTR_ENC_MODE_TRNS:
				break;
#ifdef ENABLE_NATT
			case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_RFC:
			case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC:
			case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_DRAFT:
			case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT:
				plog(LLV_DEBUG, LOCATION, NULL,
				     "UDP encapsulation requested\n");
				break;
#endif
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid encryption mode=%u.\n",
					lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_AUTH:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when AUTH.\n");
				return -1;
			}

			switch (lorv) {
			case IPSECDOI_ATTR_AUTH_HMAC_MD5:
				if (proto_id == IPSECDOI_PROTO_IPSEC_AH &&
				    trns->t_id != IPSECDOI_AH_MD5) {
ahmismatch:
					plog(LLV_ERROR, LOCATION, NULL,
						"auth algorithm %u conflicts "
						"with transform %u.\n",
						lorv, trns->t_id);
					return -1;
				}
				break;
			case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
				if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
					if (trns->t_id != IPSECDOI_AH_SHA)
						goto ahmismatch;
				}
				break;
 			case IPSECDOI_ATTR_AUTH_HMAC_SHA2_256:
 				if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
 					if (trns->t_id != IPSECDOI_AH_SHA256)
 						goto ahmismatch;
 				}
 				break;
 			case IPSECDOI_ATTR_AUTH_HMAC_SHA2_384:
 				if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
 					if (trns->t_id != IPSECDOI_AH_SHA384)
 						goto ahmismatch;
 				}
 				break;
 			case IPSECDOI_ATTR_AUTH_HMAC_SHA2_512:
 				if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
 					if (trns->t_id != IPSECDOI_AH_SHA512)
 					goto ahmismatch;
 				}
 				break;
			case IPSECDOI_ATTR_AUTH_DES_MAC:
			case IPSECDOI_ATTR_AUTH_KPDK:
				plog(LLV_ERROR, LOCATION, NULL,
					"auth algorithm %u isn't supported.\n",
					lorv);
				return -1;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid auth algorithm=%u.\n",
					lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_SA_LD_TYPE:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when LD_TYPE.\n");
				return -1;
			}

			switch (lorv) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid life type %d.\n", lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_SA_LD:
			if (flag) {
				/* i.e. ISAKMP_GEN_TV */
				plog(LLV_DEBUG, LOCATION, NULL,
					"life duration was in TLV.\n");
			} else {
				/* i.e. ISAKMP_GEN_TLV */
				if (lorv == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid length of LD\n");
					return -1;
				}
			}
			break;

		case IPSECDOI_ATTR_GRP_DESC:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when GRP_DESC.\n");
				return -1;
			}

			if (!alg_oakley_dhdef_ok(lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid group description=%u.\n",
					lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_KEY_LENGTH:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when KEY_LENGTH.\n");
				return -1;
			}
			break;

#ifdef HAVE_SECCTX
		case IPSECDOI_ATTR_SECCTX:
			if (flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"SECCTX must be in TLV.\n");
				return -1;
			}
		break;
#endif

		case IPSECDOI_ATTR_KEY_ROUNDS:
		case IPSECDOI_ATTR_COMP_DICT_SIZE:
		case IPSECDOI_ATTR_COMP_PRIVALG:
			plog(LLV_ERROR, LOCATION, NULL,
				"attr type=%u isn't supported.\n", type);
			return -1;

		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid attribute type %d.\n", type);
			return -1;
		}

		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + lorv);
			d = (struct isakmp_data *)((caddr_t)d
				+ sizeof(*d) + lorv);
		}
	}

	if (proto_id == IPSECDOI_PROTO_IPSEC_AH &&
	    !attrseen[IPSECDOI_ATTR_AUTH]) {
		plog(LLV_ERROR, LOCATION, NULL,
			"attr AUTH must be present for AH.\n");
		return -1;
	}

	if (proto_id == IPSECDOI_PROTO_IPSEC_ESP &&
	    trns->t_id == IPSECDOI_ESP_NULL &&
	    !attrseen[IPSECDOI_ATTR_AUTH]) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "attr AUTH must be present for ESP NULL encryption.\n");
		return -1;
	}

	return 0;
}

static int
check_attr_ipcomp(trns)
	struct isakmp_pl_t *trns;
{
	struct isakmp_data *d;
	int tlen;
	int flag, type = 0;
	u_int16_t lorv;
	int attrseen[16];	/* XXX magic number */

	tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
	d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));
	memset(attrseen, 0, sizeof(attrseen));

	while (tlen > 0) {
		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;
		lorv = ntohs(d->lorv);

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%d, flag=0x%04x, lorv=0x%04x\n",
			type, flag, lorv);

		if (type < sizeof(attrseen)/sizeof(attrseen[0]))
			attrseen[type]++;

		switch (type) {
		case IPSECDOI_ATTR_ENC_MODE:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when ENC_MODE.\n");
				return -1;
			}

			switch (lorv) {
			case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
			case IPSECDOI_ATTR_ENC_MODE_TRNS:
				break;
#ifdef ENABLE_NATT
			case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_RFC:
			case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC:
			case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_DRAFT:
			case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT:
				plog(LLV_DEBUG, LOCATION, NULL,
				     "UDP encapsulation requested\n");
				break;
#endif
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid encryption mode=%u.\n",
					lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_SA_LD_TYPE:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when LD_TYPE.\n");
				return -1;
			}

			switch (lorv) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				break;
			default:
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid life type %d.\n", lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_SA_LD:
			if (flag) {
				/* i.e. ISAKMP_GEN_TV */
				plog(LLV_DEBUG, LOCATION, NULL,
					"life duration was in TLV.\n");
			} else {
				/* i.e. ISAKMP_GEN_TLV */
				if (lorv == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid length of LD\n");
					return -1;
				}
			}
			break;

		case IPSECDOI_ATTR_GRP_DESC:
			if (! flag) {
				plog(LLV_ERROR, LOCATION, NULL,
					"must be TV when GRP_DESC.\n");
				return -1;
			}

			if (!alg_oakley_dhdef_ok(lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid group description=%u.\n",
					lorv);
				return -1;
			}
			break;

		case IPSECDOI_ATTR_AUTH:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid attr type=%u.\n", type);
			return -1;

		case IPSECDOI_ATTR_KEY_LENGTH:
		case IPSECDOI_ATTR_KEY_ROUNDS:
		case IPSECDOI_ATTR_COMP_DICT_SIZE:
		case IPSECDOI_ATTR_COMP_PRIVALG:
			plog(LLV_ERROR, LOCATION, NULL,
				"attr type=%u isn't supported.\n", type);
			return -1;

		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid attribute type %d.\n", type);
			return -1;
		}

		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d
				+ sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + lorv);
			d = (struct isakmp_data *)((caddr_t)d
				+ sizeof(*d) + lorv);
		}
	}

#if 0
	if (proto_id == IPSECDOI_PROTO_IPCOMP &&
	    !attrseen[IPSECDOI_ATTR_AUTH]) {
		plog(LLV_ERROR, LOCATION, NULL,
			"attr AUTH must be present for AH.\n", type);
		return -1;
	}
#endif

	return 0;
}

/* %%% */
/*
 * create phase1 proposal from remote configuration.
 * NOT INCLUDING isakmp general header of SA payload
 */
vchar_t *
ipsecdoi_setph1proposal(rmconf, props)
	struct remoteconf *rmconf;
	struct isakmpsa *props;
{
	vchar_t *mysa;
	int sablen;

	/* count total size of SA minus isakmp general header */
	/* not including isakmp general header of SA payload */
	sablen = sizeof(struct ipsecdoi_sa_b);
	sablen += setph1prop(props, NULL);

	mysa = vmalloc(sablen);
	if (mysa == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate my sa buffer\n");
		return NULL;
	}

	/* create SA payload */
	/* not including isakmp general header */
	((struct ipsecdoi_sa_b *)mysa->v)->doi = htonl(rmconf->doitype);
	((struct ipsecdoi_sa_b *)mysa->v)->sit = htonl(rmconf->sittype);

	(void)setph1prop(props, mysa->v + sizeof(struct ipsecdoi_sa_b));

	return mysa;
}

static int
setph1prop(props, buf)
	struct isakmpsa *props;
	caddr_t buf;
{
	struct isakmp_pl_p *prop = NULL;
	struct isakmpsa *s = NULL;
	int proplen, trnslen;
	u_int8_t *np_t; /* pointer next trns type in previous header */
	int trns_num;
	caddr_t p = buf;

	proplen = sizeof(*prop);
	if (buf) {
		/* create proposal */
		prop = (struct isakmp_pl_p *)p;
		prop->h.np = ISAKMP_NPTYPE_NONE;
		prop->p_no = props->prop_no;
		prop->proto_id = IPSECDOI_PROTO_ISAKMP;
		prop->spi_size = 0;
		p += sizeof(*prop);
	}

	np_t = NULL;
	trns_num = 0;

	for (s = props; s != NULL; s = s->next) {
		if (np_t)
			*np_t = ISAKMP_NPTYPE_T;

		trnslen = setph1trns(s, p);
		proplen += trnslen;
		if (buf) {
			/* save buffer to pre-next payload */
			np_t = &((struct isakmp_pl_t *)p)->h.np;
			p += trnslen;

			/* count up transform length */
			trns_num++;
		}
	}

	/* update proposal length */
	if (buf) {
		prop->h.len = htons(proplen);
		prop->num_t = trns_num;
	}

	return proplen;
}

static int
setph1trns(sa, buf)
	struct isakmpsa *sa;
	caddr_t buf;
{
	struct isakmp_pl_t *trns = NULL;
	int trnslen, attrlen;
	caddr_t p = buf;

	trnslen = sizeof(*trns);
	if (buf) {
		/* create transform */
		trns = (struct isakmp_pl_t *)p;
		trns->h.np  = ISAKMP_NPTYPE_NONE;
		trns->t_no  = sa->trns_no;
		trns->t_id  = IPSECDOI_KEY_IKE;
		p += sizeof(*trns);
	}

	attrlen = setph1attr(sa, p);
	trnslen += attrlen;
	if (buf)
		p += attrlen;

	if (buf)
		trns->h.len = htons(trnslen);

	return trnslen;
}

static int
setph1attr(sa, buf)
	struct isakmpsa *sa;
	caddr_t buf;
{
	caddr_t p = buf;
	int attrlen = 0;

	if (sa->lifetime) {
		u_int32_t lifetime = htonl((u_int32_t)sa->lifetime);

		attrlen += sizeof(struct isakmp_data)
			+ sizeof(struct isakmp_data);
		if (sa->lifetime > 0xffff)
			attrlen += sizeof(lifetime);
		if (buf) {
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD_TYPE,
						OAKLEY_ATTR_SA_LD_TYPE_SEC);
			if (sa->lifetime > 0xffff) {
				p = isakmp_set_attr_v(p, OAKLEY_ATTR_SA_LD,
						(caddr_t)&lifetime,
						sizeof(lifetime));
			} else {
				p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD,
							sa->lifetime);
			}
		}
	}

	if (sa->lifebyte) {
		u_int32_t lifebyte = htonl((u_int32_t)sa->lifebyte);

		attrlen += sizeof(struct isakmp_data)
			+ sizeof(struct isakmp_data);
		if (sa->lifebyte > 0xffff)
			attrlen += sizeof(lifebyte);
		if (buf) {
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD_TYPE,
						OAKLEY_ATTR_SA_LD_TYPE_KB);
			if (sa->lifebyte > 0xffff) {
				p = isakmp_set_attr_v(p, OAKLEY_ATTR_SA_LD,
							(caddr_t)&lifebyte,
							sizeof(lifebyte));
			} else {
				p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD,
							sa->lifebyte);
			}
		}
	}

	if (sa->enctype) {
		attrlen += sizeof(struct isakmp_data);
		if (buf)
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_ENC_ALG, sa->enctype);
	}
	if (sa->encklen) {
		attrlen += sizeof(struct isakmp_data);
		if (buf)
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_KEY_LEN, sa->encklen);
	}
	if (sa->authmethod) {
		int authmethod;

		authmethod = isakmpsa_switch_authmethod(sa->authmethod);
		authmethod &= 0xffff;
		attrlen += sizeof(struct isakmp_data);
		if (buf)
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_AUTH_METHOD, authmethod);
	}
	if (sa->hashtype) {
		attrlen += sizeof(struct isakmp_data);
		if (buf)
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_HASH_ALG, sa->hashtype);
	}
	switch (sa->dh_group) {
	case OAKLEY_ATTR_GRP_DESC_MODP768:
	case OAKLEY_ATTR_GRP_DESC_MODP1024:
	case OAKLEY_ATTR_GRP_DESC_MODP1536:
	case OAKLEY_ATTR_GRP_DESC_MODP2048:
	case OAKLEY_ATTR_GRP_DESC_MODP3072:
	case OAKLEY_ATTR_GRP_DESC_MODP4096:
	case OAKLEY_ATTR_GRP_DESC_MODP6144:
	case OAKLEY_ATTR_GRP_DESC_MODP8192:
		/* don't attach group type for known groups */
		attrlen += sizeof(struct isakmp_data);
		if (buf) {
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_GRP_DESC,
				sa->dh_group);
		}
		break;
	case OAKLEY_ATTR_GRP_DESC_EC2N155:
	case OAKLEY_ATTR_GRP_DESC_EC2N185:
		/* don't attach group type for known groups */
		attrlen += sizeof(struct isakmp_data);
		if (buf) {
			p = isakmp_set_attr_l(p, OAKLEY_ATTR_GRP_TYPE,
				OAKLEY_ATTR_GRP_TYPE_EC2N);
		}
		break;
	case 0:
	default:
		break;
	}

#ifdef HAVE_GSSAPI
	if (sa->authmethod == OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB &&
	    sa->gssid != NULL) {
		attrlen += sizeof(struct isakmp_data);
		/*
		 * Older versions of racoon just placed the ISO-Latin-1
		 * string on the wire directly.  Check to see if we are
		 * configured to be compatible with this behavior.  Otherwise,
		 * we encode the GSS ID as UTF-16LE for Windows 2000
		 * compatibility, which requires twice the number of octets.
		 */
		if (lcconf->gss_id_enc == LC_GSSENC_LATIN1)
			attrlen += sa->gssid->l;
		else
			attrlen += sa->gssid->l * 2;
		if (buf) {
			plog(LLV_DEBUG, LOCATION, NULL, "gss id attr: len %zu, "
			    "val '%.*s'\n", sa->gssid->l, (int)sa->gssid->l,
			    sa->gssid->v);
			if (lcconf->gss_id_enc == LC_GSSENC_LATIN1) {
				p = isakmp_set_attr_v(p, OAKLEY_ATTR_GSS_ID,
					(caddr_t)sa->gssid->v,
					sa->gssid->l);
			} else {
				size_t dstleft = sa->gssid->l * 2;
				size_t srcleft = sa->gssid->l;
				const char *src = (const char *)sa->gssid->v;
				char *odst, *dst = racoon_malloc(dstleft);
				iconv_t cd;
				size_t rv;

				cd = iconv_open("utf-16le", "latin1");
				if (cd == (iconv_t) -1) {
					plog(LLV_ERROR, LOCATION, NULL,
					    "unable to initialize "
					    "latin1 -> utf-16le "
					    "converstion descriptor: %s\n",
					    strerror(errno));
					attrlen -= sa->gssid->l * 2;
					goto gssid_done;
				}
				odst = dst;
				rv = iconv(cd, (__iconv_const char **)&src,
				    &srcleft, &dst, &dstleft);
				if (rv != 0) {
					if (rv == -1) {
						plog(LLV_ERROR, LOCATION, NULL,
						    "unable to convert GSS ID "
						    "from latin1 -> utf-16le: "
						    "%s\n", strerror(errno));
					} else {
						/* should never happen */
						plog(LLV_ERROR, LOCATION, NULL,
						    "%zd character%s in GSS ID "
						    "cannot be represented "
						    "in utf-16le\n",
						    rv, rv == 1 ? "" : "s");
					}
					(void) iconv_close(cd);
					attrlen -= sa->gssid->l * 2;
					goto gssid_done;
				}
				(void) iconv_close(cd);

				/* XXX Check srcleft and dstleft? */

				p = isakmp_set_attr_v(p, OAKLEY_ATTR_GSS_ID,
					odst, sa->gssid->l * 2);

				racoon_free(odst);
			}
		}
	}
 gssid_done:
#endif /* HAVE_GSSAPI */

	return attrlen;
}

static vchar_t *
setph2proposal0(iph2, pp, pr)
	const struct ph2handle *iph2;
	const struct saprop *pp;
	const struct saproto *pr;
{
	vchar_t *p;
	struct isakmp_pl_p *prop;
	struct isakmp_pl_t *trns;
	struct satrns *tr;
	int attrlen;
	size_t trnsoff;
	caddr_t x0, x;
	u_int8_t *np_t; /* pointer next trns type in previous header */
	const u_int8_t *spi;
#ifdef HAVE_SECCTX
	int truectxlen = 0;
#endif

	p = vmalloc(sizeof(*prop) + sizeof(pr->spi));
	if (p == NULL)
		return NULL;

	/* create proposal */
	prop = (struct isakmp_pl_p *)p->v;
	prop->h.np = ISAKMP_NPTYPE_NONE;
	prop->p_no = pp->prop_no;
	prop->proto_id = pr->proto_id;
	prop->num_t = 1;

	spi = (const u_int8_t *)&pr->spi;
	switch (pr->proto_id) {
	case IPSECDOI_PROTO_IPCOMP:
		/*
		 * draft-shacham-ippcp-rfc2393bis-05.txt:
		 * construct 16bit SPI (CPI).
		 * XXX we may need to provide a configuration option to
		 * generate 32bit SPI.  otherwise we cannot interoeprate
		 * with nodes that uses 32bit SPI, in case we are initiator.
		 */
		prop->spi_size = sizeof(u_int16_t);
		spi += sizeof(pr->spi) - sizeof(u_int16_t);
		p->l -= sizeof(pr->spi);
		p->l += sizeof(u_int16_t);
		break;
	default:
		prop->spi_size = sizeof(pr->spi);
		break;
	}
	memcpy(prop + 1, spi, prop->spi_size);

	/* create transform */
	trnsoff = sizeof(*prop) + prop->spi_size;
	np_t = NULL;

	for (tr = pr->head; tr; tr = tr->next) {

		switch (pr->proto_id) {
		case IPSECDOI_PROTO_IPSEC_ESP:
			/*
			 * don't build a null encryption
			 * with no authentication transform.
			 */
			if (tr->trns_id == IPSECDOI_ESP_NULL &&
			    tr->authtype == IPSECDOI_ATTR_AUTH_NONE)
				continue;
			break;
		}

		if (np_t) {
			*np_t = ISAKMP_NPTYPE_T;
			prop->num_t++;
		}

		/* get attribute length */
		attrlen = 0;
		if (pp->lifetime) {
			attrlen += sizeof(struct isakmp_data)
				+ sizeof(struct isakmp_data);
			if (pp->lifetime > 0xffff)
				attrlen += sizeof(u_int32_t);
		}
		if (pp->lifebyte && pp->lifebyte != IPSECDOI_ATTR_SA_LD_KB_MAX) {
			attrlen += sizeof(struct isakmp_data)
				+ sizeof(struct isakmp_data);
			if (pp->lifebyte > 0xffff)
				attrlen += sizeof(u_int32_t);
		}
		attrlen += sizeof(struct isakmp_data);	/* enc mode */
		if (tr->encklen)
			attrlen += sizeof(struct isakmp_data);

		switch (pr->proto_id) {
		case IPSECDOI_PROTO_IPSEC_ESP:
			/* non authentication mode ? */
			if (tr->authtype != IPSECDOI_ATTR_AUTH_NONE)
				attrlen += sizeof(struct isakmp_data);
			break;
		case IPSECDOI_PROTO_IPSEC_AH:
			if (tr->authtype == IPSECDOI_ATTR_AUTH_NONE) {
				plog(LLV_ERROR, LOCATION, NULL,
					"no authentication algorithm found "
					"but protocol is AH.\n");
				vfree(p);
				return NULL;
			}
			attrlen += sizeof(struct isakmp_data);
			break;
		case IPSECDOI_PROTO_IPCOMP:
			break;
		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid protocol: %d\n", pr->proto_id);
			vfree(p);
			return NULL;
		}

		if (alg_oakley_dhdef_ok(iph2->sainfo->pfs_group))
			attrlen += sizeof(struct isakmp_data);

#ifdef HAVE_SECCTX
		/* ctx_str is defined as char ctx_str[MAX_CTXSTR_SIZ].
		 * The string may be smaller than MAX_CTXSTR_SIZ.
		 */
		if (*pp->sctx.ctx_str) {
			truectxlen = sizeof(struct security_ctx) -
				     (MAX_CTXSTR_SIZE - pp->sctx.ctx_strlen);
			attrlen += sizeof(struct isakmp_data) + truectxlen;
		}
#endif /* HAVE_SECCTX */

		p = vrealloc(p, p->l + sizeof(*trns) + attrlen);
		if (p == NULL)
			return NULL;
		prop = (struct isakmp_pl_p *)p->v;

		/* set transform's values */
		trns = (struct isakmp_pl_t *)(p->v + trnsoff);
		trns->h.np  = ISAKMP_NPTYPE_NONE;
		trns->t_no  = tr->trns_no;
		trns->t_id  = tr->trns_id;

		/* set attributes */
		x = x0 = p->v + trnsoff + sizeof(*trns);

		if (pp->lifetime) {
			x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD_TYPE,
						IPSECDOI_ATTR_SA_LD_TYPE_SEC);
			if (pp->lifetime > 0xffff) {
				u_int32_t v = htonl((u_int32_t)pp->lifetime);
				x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SA_LD,
							(caddr_t)&v, sizeof(v));
			} else {
				x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD,
							pp->lifetime);
			}
		}

		if (pp->lifebyte && pp->lifebyte != IPSECDOI_ATTR_SA_LD_KB_MAX) {
			x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD_TYPE,
						IPSECDOI_ATTR_SA_LD_TYPE_KB);
			if (pp->lifebyte > 0xffff) {
				u_int32_t v = htonl((u_int32_t)pp->lifebyte);
				x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SA_LD,
							(caddr_t)&v, sizeof(v));
			} else {
				x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD,
							pp->lifebyte);
			}
		}

		x = isakmp_set_attr_l(x, IPSECDOI_ATTR_ENC_MODE, pr->encmode);

		if (tr->encklen)
			x = isakmp_set_attr_l(x, IPSECDOI_ATTR_KEY_LENGTH, tr->encklen);

		/* mandatory check has done above. */
		if ((pr->proto_id == IPSECDOI_PROTO_IPSEC_ESP && tr->authtype != IPSECDOI_ATTR_AUTH_NONE)
		 || pr->proto_id == IPSECDOI_PROTO_IPSEC_AH)
			x = isakmp_set_attr_l(x, IPSECDOI_ATTR_AUTH, tr->authtype);

		if (alg_oakley_dhdef_ok(iph2->sainfo->pfs_group))
			x = isakmp_set_attr_l(x, IPSECDOI_ATTR_GRP_DESC,
				iph2->sainfo->pfs_group);

#ifdef HAVE_SECCTX
		if (*pp->sctx.ctx_str) {
			struct security_ctx secctx;
			secctx = pp->sctx;
			secctx.ctx_strlen = htons(pp->sctx.ctx_strlen);
			x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SECCTX,
					     (caddr_t)&secctx, truectxlen);
		}
#endif
		/* update length of this transform. */
		trns = (struct isakmp_pl_t *)(p->v + trnsoff);
		trns->h.len = htons(sizeof(*trns) + attrlen);

		/* save buffer to pre-next payload */
		np_t = &trns->h.np;

		trnsoff += (sizeof(*trns) + attrlen);
	}

	if (np_t == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no suitable proposal was created.\n");
		return NULL;
	}

	/* update length of this protocol. */
	prop->h.len = htons(p->l);

	return p;
}

/*
 * create phase2 proposal from policy configuration.
 * NOT INCLUDING isakmp general header of SA payload.
 * This function is called by initiator only.
 */
int
ipsecdoi_setph2proposal(iph2)
	struct ph2handle *iph2;
{
	struct saprop *proposal, *a;
	struct saproto *b = NULL;
	vchar_t *q;
	struct ipsecdoi_sa_b *sab;
	struct isakmp_pl_p *prop;
	size_t propoff;	/* for previous field of type of next payload. */

	proposal = iph2->proposal;

	iph2->sa = vmalloc(sizeof(*sab));
	if (iph2->sa == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate my sa buffer\n");
		return -1;
	}

	/* create SA payload */
	sab = (struct ipsecdoi_sa_b *)iph2->sa->v;
	sab->doi = htonl(IPSEC_DOI);
	sab->sit = htonl(IPSECDOI_SIT_IDENTITY_ONLY);	/* XXX configurable ? */

	prop = NULL;
	propoff = 0;
	for (a = proposal; a; a = a->next) {
		for (b = a->head; b; b = b->next) {
#ifdef ENABLE_NATT
			if (iph2->ph1->natt_flags & NAT_DETECTED) {
			  int udp_diff = iph2->ph1->natt_options->mode_udp_diff;
			  plog (LLV_INFO, LOCATION, NULL,
				"NAT detected -> UDP encapsulation "
				"(ENC_MODE %d->%d).\n",
				b->encmode,
				b->encmode+udp_diff);
			  /* Tunnel -> UDP-Tunnel, Transport -> UDP_Transport */
			  b->encmode += udp_diff;
			  b->udp_encap = 1;
			}
#endif

			q = setph2proposal0(iph2, a, b);
			if (q == NULL) {
				VPTRINIT(iph2->sa);
				return -1;
			}

			iph2->sa = vrealloc(iph2->sa, iph2->sa->l + q->l);
			if (iph2->sa == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to allocate my sa buffer\n");
				if (q)
					vfree(q);
				return -1;
			}
			memcpy(iph2->sa->v + iph2->sa->l - q->l, q->v, q->l);
			if (propoff != 0) {
				prop = (struct isakmp_pl_p *)(iph2->sa->v +
					propoff);
				prop->h.np = ISAKMP_NPTYPE_P;
			}
			propoff = iph2->sa->l - q->l;

			vfree(q);
		}
	}

	return 0;
}

/*
 * return 1 if all of the given protocols are transport mode.
 */
int
ipsecdoi_transportmode(pp)
	struct saprop *pp;
{
	struct saproto *pr = NULL;

	for (; pp; pp = pp->next) {
		for (pr = pp->head; pr; pr = pr->next) {
			if (pr->encmode != IPSECDOI_ATTR_ENC_MODE_TRNS &&
			    pr->encmode != IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC &&
			    pr->encmode != IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT)
				return 0;
		}
	}

	return 1;
}

int
ipsecdoi_get_defaultlifetime()
{
	return IPSECDOI_ATTR_SA_LD_SEC_DEFAULT;
}

int
ipsecdoi_checkalgtypes(proto_id, enc, auth, comp)
	int proto_id, enc, auth, comp;
{
#define TMPALGTYPE2STR(n) s_algtype(algclass_ipsec_##n, n)
	switch (proto_id) {
	case IPSECDOI_PROTO_IPSEC_ESP:
		if (enc == 0 || comp != 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"illegal algorithm defined "
				"ESP enc=%s auth=%s comp=%s.\n",
				TMPALGTYPE2STR(enc),
				TMPALGTYPE2STR(auth),
				TMPALGTYPE2STR(comp));
			return -1;
		}
		break;
	case IPSECDOI_PROTO_IPSEC_AH:
		if (enc != 0 || auth == 0 || comp != 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"illegal algorithm defined "
				"AH enc=%s auth=%s comp=%s.\n",
				TMPALGTYPE2STR(enc),
				TMPALGTYPE2STR(auth),
				TMPALGTYPE2STR(comp));
			return -1;
		}
		break;
	case IPSECDOI_PROTO_IPCOMP:
		if (enc != 0 || auth != 0 || comp == 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"illegal algorithm defined "
				"IPcomp enc=%s auth=%s comp=%s.\n",
				TMPALGTYPE2STR(enc),
				TMPALGTYPE2STR(auth),
				TMPALGTYPE2STR(comp));
			return -1;
		}
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid ipsec protocol %d\n", proto_id);
		return -1;
	}
#undef TMPALGTYPE2STR
	return 0;
}

int
ipproto2doi(proto)
	int proto;
{
	switch (proto) {
	case IPPROTO_AH:
		return IPSECDOI_PROTO_IPSEC_AH;
	case IPPROTO_ESP:
		return IPSECDOI_PROTO_IPSEC_ESP;
	case IPPROTO_IPCOMP:
		return IPSECDOI_PROTO_IPCOMP;
	}
	return -1;	/* XXX */
}

int
doi2ipproto(proto)
	int proto;
{
	switch (proto) {
	case IPSECDOI_PROTO_IPSEC_AH:
		return IPPROTO_AH;
	case IPSECDOI_PROTO_IPSEC_ESP:
		return IPPROTO_ESP;
	case IPSECDOI_PROTO_IPCOMP:
		return IPPROTO_IPCOMP;
	}
	return -1;	/* XXX */
}

/*
 * Check if a subnet id is valid for comparison
 * with an address id ( address length mask )
 * and compare them
 * Return value
 * =  0 for match
 * =  1 for mismatch
 */

int
ipsecdoi_subnetisaddr_v4(const vchar_t *subnet, const vchar_t *address)
{
	struct in_addr *mask;

	if (address->l != sizeof(struct in_addr))
		return 1;

	if (subnet->l != (sizeof(struct in_addr)*2))
		return 1;

	mask = (struct in_addr*)(subnet->v + sizeof(struct in_addr));

	if (mask->s_addr!=0xffffffff)
		return 1;

	return memcmp(subnet->v,address->v,address->l);
}

#ifdef INET6

int
ipsecdoi_subnetisaddr_v6(const vchar_t *subnet, const vchar_t *address)
{
	struct in6_addr *mask;
	int i;

	if (address->l != sizeof(struct in6_addr))
		return 1;

	if (subnet->l != (sizeof(struct in6_addr)*2))
		return 1;

	mask = (struct in6_addr*)(subnet->v + sizeof(struct in6_addr));

	for (i=0; i<16; i++)
		if(mask->s6_addr[i]!=0xff)
			return 1;

	return memcmp(subnet->v,address->v,address->l);
}

#endif

/*
 * Check and Compare two IDs
 * - specify 0 for exact if wildcards are allowed
 * Return value
 * =  0 for match
 * =  1 for misatch
 * = -1 for integrity error
 */

int
ipsecdoi_chkcmpids( idt, ids, exact )
	const vchar_t *idt; /* id cmp target */
	const vchar_t *ids; /* id cmp source */
	int exact;
{
	struct ipsecdoi_id_b *id_bt;
	struct ipsecdoi_id_b *id_bs;
	vchar_t ident_t;
	vchar_t ident_s;
	int result;

	/* handle wildcard IDs */

	if (idt == NULL || ids == NULL)
	{
		if( !exact )
		{
			plog(LLV_DEBUG, LOCATION, NULL,
				"check and compare ids : values matched (ANONYMOUS)\n" );
			return 0;
		}
		else
		{
			plog(LLV_DEBUG, LOCATION, NULL,
				"check and compare ids : value mismatch (ANONYMOUS)\n" );
			return -1;
		}
	}

	/* make sure the ids are of the same type */

	id_bt = (struct ipsecdoi_id_b *) idt->v;
	id_bs = (struct ipsecdoi_id_b *) ids->v;

	ident_t.v = idt->v + sizeof(*id_bt);
	ident_t.l = idt->l - sizeof(*id_bt);
	ident_s.v = ids->v + sizeof(*id_bs);
	ident_s.l = ids->l - sizeof(*id_bs);

	if (id_bs->type != id_bt->type)
	{
		/*
		 * special exception for comparing
                 * address to subnet id types when
                 * the netmask is address length
                 */

		if ((id_bs->type == IPSECDOI_ID_IPV4_ADDR)&&
		    (id_bt->type == IPSECDOI_ID_IPV4_ADDR_SUBNET)) {
			result = ipsecdoi_subnetisaddr_v4(&ident_t,&ident_s);
			goto cmpid_result;
		}

		if ((id_bs->type == IPSECDOI_ID_IPV4_ADDR_SUBNET)&&
		    (id_bt->type == IPSECDOI_ID_IPV4_ADDR)) {
			result = ipsecdoi_subnetisaddr_v4(&ident_s,&ident_t);
			goto cmpid_result;
		}

#ifdef INET6
		if ((id_bs->type == IPSECDOI_ID_IPV6_ADDR)&&
		    (id_bt->type == IPSECDOI_ID_IPV6_ADDR_SUBNET)) {
			result = ipsecdoi_subnetisaddr_v6(&ident_t,&ident_s);
			goto cmpid_result;
		}

		if ((id_bs->type == IPSECDOI_ID_IPV6_ADDR_SUBNET)&&
		    (id_bt->type == IPSECDOI_ID_IPV6_ADDR)) {
			result = ipsecdoi_subnetisaddr_v6(&ident_s,&ident_t);
			goto cmpid_result;
		}
#endif
		plog(LLV_DEBUG, LOCATION, NULL,
			"check and compare ids : id type mismatch %s != %s\n",
			s_ipsecdoi_ident(id_bs->type),
			s_ipsecdoi_ident(id_bt->type));

		return 1;
	}

	if(id_bs->proto_id != id_bt->proto_id){
		plog(LLV_DEBUG, LOCATION, NULL,
			"check and compare ids : proto_id mismatch %d != %d\n",
			id_bs->proto_id, id_bt->proto_id);

		return 1;
	}

	/* compare the ID data. */

	switch (id_bt->type) {
	        case IPSECDOI_ID_DER_ASN1_DN:
        	case IPSECDOI_ID_DER_ASN1_GN:
			/* compare asn1 ids */
			result = eay_cmp_asn1dn(&ident_t, &ident_s);
			goto cmpid_result;

		case IPSECDOI_ID_IPV4_ADDR:
			/* validate lengths */
			if ((ident_t.l != sizeof(struct in_addr))||
			    (ident_s.l != sizeof(struct in_addr)))
				goto cmpid_invalid;
			break;

		case IPSECDOI_ID_IPV4_ADDR_SUBNET:
		case IPSECDOI_ID_IPV4_ADDR_RANGE:
			/* validate lengths */
			if ((ident_t.l != (sizeof(struct in_addr)*2))||
			    (ident_s.l != (sizeof(struct in_addr)*2)))
				goto cmpid_invalid;
			break;

#ifdef INET6
		case IPSECDOI_ID_IPV6_ADDR:
			/* validate lengths */
			if ((ident_t.l != sizeof(struct in6_addr))||
			    (ident_s.l != sizeof(struct in6_addr)))
				goto cmpid_invalid;
			break;

		case IPSECDOI_ID_IPV6_ADDR_SUBNET:
		case IPSECDOI_ID_IPV6_ADDR_RANGE:
			/* validate lengths */
			if ((ident_t.l != (sizeof(struct in6_addr)*2))||
			    (ident_s.l != (sizeof(struct in6_addr)*2)))
				goto cmpid_invalid;
			break;
#endif
		case IPSECDOI_ID_FQDN:
		case IPSECDOI_ID_USER_FQDN:
		case IPSECDOI_ID_KEY_ID:
			break;

		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"Unhandled id type %i specified for comparison\n",
				id_bt->type);
			return -1;
	}

	/* validate matching data and length */
	if (ident_t.l == ident_s.l)
		result = memcmp(ident_t.v,ident_s.v,ident_t.l);
	else
		result = 1;

cmpid_result:

	/* debug level output */
	if(loglevel >= LLV_DEBUG) {
		char *idstrt = ipsecdoi_id2str(idt);
		char *idstrs = ipsecdoi_id2str(ids);

		if (!result)
	 		plog(LLV_DEBUG, LOCATION, NULL,
				"check and compare ids : values matched (%s)\n",
				 s_ipsecdoi_ident(id_bs->type) );
		else
 			plog(LLV_DEBUG, LOCATION, NULL,
				"check and compare ids : value mismatch (%s)\n",
				 s_ipsecdoi_ident(id_bs->type));

		plog(LLV_DEBUG, LOCATION, NULL, "cmpid target: \'%s\'\n", idstrt );
		plog(LLV_DEBUG, LOCATION, NULL, "cmpid source: \'%s\'\n", idstrs );

		racoon_free(idstrs);
		racoon_free(idstrt);
	}

	/* return result */
	if( !result )
		return 0;
	else
		return 1;

cmpid_invalid:

	/* id integrity error */
	plog(LLV_DEBUG, LOCATION, NULL, "check and compare ids : %s integrity error\n",
		s_ipsecdoi_ident(id_bs->type));
	plog(LLV_DEBUG, LOCATION, NULL, "cmpid target: length = \'%zu\'\n", ident_t.l );
	plog(LLV_DEBUG, LOCATION, NULL, "cmpid source: length = \'%zu\'\n", ident_s.l );

	return -1;
}

/*
 * check the following:
 * - In main mode with pre-shared key, only address type can be used.
 * - if proper type for phase 1 ?
 * - if phase 1 ID payload conformed RFC2407 4.6.2.
 *   (proto, port) must be (0, 0), (udp, 500) or (udp, [specified]).
 * - if ID payload sent from peer is equal to the ID expected by me.
 *
 * both of "id" and "id_p" should be ID payload without general header,
 */
int
ipsecdoi_checkid1(iph1)
	struct ph1handle *iph1;
{
	struct ipsecdoi_id_b *id_b;

	if (iph1->id_p == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid iph1 passed id_p == NULL\n");
		return ISAKMP_INTERNAL_ERROR;
	}
	if (iph1->id_p->l < sizeof(*id_b)) {
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid value passed as \"ident\" (len=%lu)\n",
			(u_long)iph1->id_p->l);
		return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
	}

	id_b = (struct ipsecdoi_id_b *)iph1->id_p->v;

	/* In main mode with pre-shared key, only address type can be used. */
	if (iph1->etype == ISAKMP_ETYPE_IDENT &&
	    iph1->approval->authmethod == OAKLEY_ATTR_AUTH_METHOD_PSKEY) {
		 if (id_b->type != IPSECDOI_ID_IPV4_ADDR
		  && id_b->type != IPSECDOI_ID_IPV6_ADDR) {
			plog(LLV_ERROR, LOCATION, NULL,
				"Expecting IP address type in main mode, "
				"but %s.\n", s_ipsecdoi_ident(id_b->type));
			return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
		}
	}

	/* if proper type for phase 1 ? */
	switch (id_b->type) {
	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
	case IPSECDOI_ID_IPV4_ADDR_RANGE:
	case IPSECDOI_ID_IPV6_ADDR_RANGE:
		plog(LLV_WARNING, LOCATION, NULL,
			"such ID type %s is not proper.\n",
			s_ipsecdoi_ident(id_b->type));
		/*FALLTHROUGH*/
	}

	/* if phase 1 ID payload conformed RFC2407 4.6.2. */
	if (id_b->type == IPSECDOI_ID_IPV4_ADDR ||
	    id_b->type == IPSECDOI_ID_IPV6_ADDR) {

		if (id_b->proto_id == 0 && ntohs(id_b->port) != 0) {
			plog(LLV_WARNING, LOCATION, NULL,
				"protocol ID and Port mismatched. "
				"proto_id:%d port:%d\n",
				id_b->proto_id, ntohs(id_b->port));
			/*FALLTHROUGH*/

		} else if (id_b->proto_id == IPPROTO_UDP) {
			/*
			 * copmaring with expecting port.
			 * always permit if port is equal to PORT_ISAKMP
			 */
			if (ntohs(id_b->port) != PORT_ISAKMP) {
				u_int16_t port;

				port = extract_port(iph1->remote);
				if (ntohs(id_b->port) != port) {
					plog(LLV_WARNING, LOCATION, NULL,
						"port %d expected, but %d\n",
						port, ntohs(id_b->port));
					/*FALLTHROUGH*/
				}
			}
		}
	}

	/* resolve remote configuration if not done yet */
	if (resolveph1rmconf(iph1) < 0)
		return ISAKMP_NTYPE_INVALID_ID_INFORMATION;

	if (iph1->rmconf == NULL)
		return ISAKMP_NTYPE_INVALID_ID_INFORMATION;

	return 0;
}

/*
 * create ID payload for phase 1 and set into iph1->id.
 * NOT INCLUDING isakmp general header.
 * see, RFC2407 4.6.2.1
 */
int
ipsecdoi_setid1(iph1)
	struct ph1handle *iph1;
{
	vchar_t *ret = NULL;
	struct ipsecdoi_id_b id_b;
	vchar_t *ident = NULL;
	struct sockaddr *ipid = NULL;

	/* init */
	id_b.proto_id = 0;
	id_b.port = 0;
	ident = NULL;

	switch (iph1->rmconf->idvtype) {
	case IDTYPE_FQDN:
		id_b.type = IPSECDOI_ID_FQDN;
		ident = vdup(iph1->rmconf->idv);
		break;
	case IDTYPE_USERFQDN:
		id_b.type = IPSECDOI_ID_USER_FQDN;
		ident = vdup(iph1->rmconf->idv);
		break;
	case IDTYPE_KEYID:
		id_b.type = IPSECDOI_ID_KEY_ID;
		ident = vdup(iph1->rmconf->idv);
		break;
	case IDTYPE_ASN1DN:
		id_b.type = IPSECDOI_ID_DER_ASN1_DN;
		if (iph1->rmconf->idv) {
			/* XXX it must be encoded to asn1dn. */
			ident = vdup(iph1->rmconf->idv);
		} else {
			if (oakley_getmycert(iph1) < 0) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to get own CERT.\n");
				goto err;
			}
			ident = eay_get_x509asn1subjectname(iph1->cert);
		}
		break;
	case IDTYPE_ADDRESS:
		/*
		 * if the value of the id type was set by the configuration
		 * file, then use it.  otherwise the value is get from local
		 * ip address by using ike negotiation.
		 */
		if (iph1->rmconf->idv)
			ipid = (struct sockaddr *)iph1->rmconf->idv->v;
		/*FALLTHROUGH*/
	default:
	    {
		int l;
		caddr_t p;

		if (ipid == NULL)
			ipid = iph1->local;

		/* use IP address */
		switch (ipid->sa_family) {
		case AF_INET:
			id_b.type = IPSECDOI_ID_IPV4_ADDR;
			l = sizeof(struct in_addr);
			p = (caddr_t)&((struct sockaddr_in *)ipid)->sin_addr;
			break;
#ifdef INET6
		case AF_INET6:
			id_b.type = IPSECDOI_ID_IPV6_ADDR;
			l = sizeof(struct in6_addr);
			p = (caddr_t)&((struct sockaddr_in6 *)ipid)->sin6_addr;
			break;
#endif
		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid address family.\n");
			goto err;
		}
		id_b.proto_id = IPPROTO_UDP;
		id_b.port = htons(PORT_ISAKMP);
		ident = vmalloc(l);
		if (!ident) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to get ID buffer.\n");
			return -1;
		}
		memcpy(ident->v, p, ident->l);
	    }
	}
	if (!ident) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID buffer.\n");
		return -1;
	}

	ret = vmalloc(sizeof(id_b) + ident->l);
	if (ret == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID buffer.\n");
		goto err;
	}

	memcpy(ret->v, &id_b, sizeof(id_b));
	memcpy(ret->v + sizeof(id_b), ident->v, ident->l);

	iph1->id = ret;

	plog(LLV_DEBUG, LOCATION, NULL,
		"use ID type of %s\n", s_ipsecdoi_ident(id_b.type));
	if (ident)
		vfree(ident);
	return 0;

err:
	if (ident)
		vfree(ident);
	plog(LLV_ERROR, LOCATION, NULL, "failed get my ID\n");
	return -1;
}

/* it's only called by cfparse.y. */
int
set_identifier(vpp, type, value)
	vchar_t **vpp;
	const vchar_t * const value;
	int type;
{
	return set_identifier_qual(vpp, type, value, IDQUAL_UNSPEC);
}

int
set_identifier_qual(vpp, type, value, qual)
	vchar_t **vpp;
	const vchar_t * const value;
	int type;
	int qual;
{
	vchar_t *new = NULL;

	/* simply return if value is null. */
	if (!value){
		if( type == IDTYPE_FQDN || type == IDTYPE_USERFQDN){
			plog(LLV_ERROR, LOCATION, NULL,
				 "No %s\n", type == IDTYPE_FQDN ? "fqdn":"user fqdn");
			return -1;
		}
		return 0;
	}

	switch (type) {
	case IDTYPE_FQDN:
	case IDTYPE_USERFQDN:
		if(value->l <= 1){
			plog(LLV_ERROR, LOCATION, NULL,
				 "Empty %s\n", type == IDTYPE_FQDN ? "fqdn":"user fqdn");
			return -1;
		}
		/* length is adjusted since QUOTEDSTRING teminates NULL. */
		new = vmalloc(value->l - 1);
		if (new == NULL)
			return -1;
		memcpy(new->v, value->v, new->l);
		break;
	case IDTYPE_KEYID:
		/*
		 * If no qualifier is specified: IDQUAL_UNSPEC. It means
		 * to use a file for backward compatibility sake.
		 */
		switch(qual) {
		case IDQUAL_FILE:
		case IDQUAL_UNSPEC: {
			FILE *fp;
			char b[512];
			int tlen, len;

			fp = fopen(value->v, "r");
			if (fp == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"can not open %s\n", value->v);
				return -1;
			}
			tlen = 0;
			while ((len = fread(b, 1, sizeof(b), fp)) != 0) {
				new = vrealloc(new, tlen + len);
				if (!new) {
					fclose(fp);
					return -1;
				}
				memcpy(new->v + tlen, b, len);
				tlen += len;
			}
			fclose(fp);
			break;
		}

		case IDQUAL_TAG:
			new = vmalloc(value->l - 1);
			if (new == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"can not allocate memory");
				return -1;
			}
			memcpy(new->v, value->v, new->l);
			break;

		default:
			plog(LLV_ERROR, LOCATION, NULL,
				"unknown qualifier");
			return -1;
		}
		break;

	case IDTYPE_ADDRESS: {
		struct sockaddr *sa;

		/* length is adjusted since QUOTEDSTRING teminates NULL. */
		if (value->l == 0)
			break;

		sa = str2saddr(value->v, NULL);
		if (sa == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid ip address %s\n", value->v);
			return -1;
		}

		new = vmalloc(sysdep_sa_len(sa));
		if (new == NULL) {
			racoon_free(sa);
			return -1;
		}
		memcpy(new->v, sa, new->l);
		racoon_free(sa);
		break;
	}
	case IDTYPE_ASN1DN:
		if (value->v[0] == '~')
			/* Hex-encoded ASN1 strings */
			new = eay_hex2asn1dn(value->v + 1, - 1);
		else
			/* DN encoded strings */
			new = eay_str2asn1dn(value->v, value->l - 1);

		if (new == NULL)
			return -1;

		if (loglevel >= LLV_DEBUG) {
			X509_NAME *xn;
			BIO *bio;
			unsigned char *ptr = (unsigned char *) new->v;
			size_t len;
			char save;

			xn = d2i_X509_NAME(NULL, (void *)&ptr, new->l);
			bio = BIO_new(BIO_s_mem());

			X509_NAME_print_ex(bio, xn, 0, 0);
			len = BIO_get_mem_data(bio, &ptr);
			save = ptr[len];
			ptr[len] = 0;
			plog(LLV_DEBUG, LOCATION, NULL, "Parsed DN: %s\n", ptr);
			ptr[len] = save;
			X509_NAME_free(xn);
			BIO_free(bio);
		}

		break;
	}

	*vpp = new;

	return 0;
}

/*
 * create ID payload for phase 2, and set into iph2->id and id_p.  There are
 * NOT INCLUDING isakmp general header.
 * this function is for initiator.  responder will get to copy from payload.
 * responder ID type is always address type.
 * see, RFC2407 4.6.2.1
 */
int
ipsecdoi_setid2(iph2)
	struct ph2handle *iph2;
{
	struct secpolicy *sp;

	/* check there is phase 2 handler ? */
	sp = getspbyspid(iph2->spid);
	if (sp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"no policy found for spid:%u.\n", iph2->spid);
		return -1;
	}

	if (!ipsecdoi_transportmode(iph2->proposal))
		iph2->id = ipsecdoi_sockaddr2id((struct sockaddr *)&sp->spidx.src,
				sp->spidx.prefs, sp->spidx.ul_proto);
	else if (iph2->sa_src != NULL) {
		/* He have a specific hint indicating that the transport
		 * mode SA will be negotiated using addresses that differ
		 * with the one from the SA. We need to indicate that to
		 * our peer by setting the SA address as ID.
		 * This is typically the case for the bootstrapping of the
		 * transport mode SA protecting BU/BA for MIPv6 traffic
		 *
		 * --arno*/
		iph2->id = ipsecdoi_sockaddr2id(iph2->sa_src,
						IPSECDOI_PREFIX_HOST,
						sp->spidx.ul_proto);
	} else
		iph2->id = ipsecdoi_sockaddr2id(iph2->src, IPSECDOI_PREFIX_HOST,
						sp->spidx.ul_proto);

	if (iph2->id == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID for %s\n",
			spidx2str(&sp->spidx));
		return -1;
	}
	plog(LLV_DEBUG, LOCATION, NULL, "use local ID type %s\n",
		s_ipsecdoi_ident(((struct ipsecdoi_id_b *)iph2->id->v)->type));

	/* remote side */
	if (!ipsecdoi_transportmode(iph2->proposal))
		iph2->id_p = ipsecdoi_sockaddr2id((struct sockaddr *)&sp->spidx.dst,
				sp->spidx.prefd, sp->spidx.ul_proto);
	else if (iph2->sa_dst != NULL) {
		/* See comment above for local side. */
		iph2->id_p = ipsecdoi_sockaddr2id(iph2->sa_dst,
						  IPSECDOI_PREFIX_HOST,
						  sp->spidx.ul_proto);
	} else
		iph2->id_p = ipsecdoi_sockaddr2id(iph2->dst, IPSECDOI_PREFIX_HOST,
			sp->spidx.ul_proto);

	if (iph2->id_p == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID for %s\n",
			spidx2str(&sp->spidx));
		VPTRINIT(iph2->id);
		return -1;
	}
	plog(LLV_DEBUG, LOCATION, NULL,
		"use remote ID type %s\n",
		s_ipsecdoi_ident(((struct ipsecdoi_id_b *)iph2->id_p->v)->type));

	return 0;
}

/*
 * set address type of ID.
 * NOT INCLUDING general header.
 */
vchar_t *
ipsecdoi_sockaddr2id(saddr, prefixlen, ul_proto)
	struct sockaddr *saddr;
	u_int prefixlen;
	u_int ul_proto;
{
	vchar_t *new;
	int type, len1, len2;
	caddr_t sa;
	u_short port;

	/*
	 * Q. When type is SUBNET, is it allowed to be ::1/128.
	 * A. Yes. (consensus at bake-off)
	 */
	switch (saddr->sa_family) {
	case AF_INET:
		len1 = sizeof(struct in_addr);
		if (prefixlen >= (sizeof(struct in_addr) << 3)) {
			type = IPSECDOI_ID_IPV4_ADDR;
			len2 = 0;
		} else {
			type = IPSECDOI_ID_IPV4_ADDR_SUBNET;
			len2 = sizeof(struct in_addr);
		}
		sa = (caddr_t)&((struct sockaddr_in *)(saddr))->sin_addr;
		port = ((struct sockaddr_in *)(saddr))->sin_port;
		break;
#ifdef INET6
	case AF_INET6:
		len1 = sizeof(struct in6_addr);
		if (prefixlen >= (sizeof(struct in6_addr) << 3)) {
			type = IPSECDOI_ID_IPV6_ADDR;
			len2 = 0;
		} else {
			type = IPSECDOI_ID_IPV6_ADDR_SUBNET;
			len2 = sizeof(struct in6_addr);
		}
		sa = (caddr_t)&((struct sockaddr_in6 *)(saddr))->sin6_addr;
		port = ((struct sockaddr_in6 *)(saddr))->sin6_port;
		break;
#endif
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid family: %d.\n", saddr->sa_family);
		return NULL;
	}

	/* get ID buffer */
	new = vmalloc(sizeof(struct ipsecdoi_id_b) + len1 + len2);
	if (new == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID buffer.\n");
		return NULL;
	}

	memset(new->v, 0, new->l);

	/* set the part of header. */
	((struct ipsecdoi_id_b *)new->v)->type = type;

	/* set ul_proto and port */
	/*
	 * NOTE: we use both IPSEC_ULPROTO_ANY and IPSEC_PORT_ANY as wild card
	 * because 0 means port number of 0.  Instead of 0, we use IPSEC_*_ANY.
	 */
	((struct ipsecdoi_id_b *)new->v)->proto_id =
		ul_proto == IPSEC_ULPROTO_ANY ? 0 : ul_proto;
	((struct ipsecdoi_id_b *)new->v)->port =
		port == IPSEC_PORT_ANY ? 0 : port;
	memcpy(new->v + sizeof(struct ipsecdoi_id_b), sa, len1);

	/* set address */

	/* set prefix */
	if (len2) {
		u_char *p = (unsigned char *) new->v +
			sizeof(struct ipsecdoi_id_b) + len1;
		u_int bits = prefixlen;

		while (bits >= 8) {
			*p++ = 0xff;
			bits -= 8;
		}

		if (bits > 0)
			*p = ~((1 << (8 - bits)) - 1);
	}

	return new;
}

vchar_t *
ipsecdoi_sockrange2id(laddr, haddr, ul_proto)
	struct sockaddr *laddr, *haddr;
	u_int ul_proto;
{
	vchar_t *new;
	int type, len1, len2;
	u_short port;

	if (laddr->sa_family != haddr->sa_family) {
	    plog(LLV_ERROR, LOCATION, NULL, "Address family mismatch\n");
	    return NULL;
	}

	switch (laddr->sa_family) {
	case AF_INET:
	    type = IPSECDOI_ID_IPV4_ADDR_RANGE;
	    len1 = sizeof(struct in_addr);
	    len2 = sizeof(struct in_addr);
	    break;
#ifdef INET6
	case AF_INET6:
		type = IPSECDOI_ID_IPV6_ADDR_RANGE;
		len1 = sizeof(struct in6_addr);
		len2 = sizeof(struct in6_addr);
		break;
#endif
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid family: %d.\n", laddr->sa_family);
		return NULL;
	}

	/* get ID buffer */
	new = vmalloc(sizeof(struct ipsecdoi_id_b) + len1 + len2);
	if (new == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get ID buffer.\n");
		return NULL;
	}

	memset(new->v, 0, new->l);
	/* set the part of header. */
	((struct ipsecdoi_id_b *)new->v)->type = type;

	/* set ul_proto and port */
	/*
	 * NOTE: we use both IPSEC_ULPROTO_ANY and IPSEC_PORT_ANY as wild card
	 * because 0 means port number of 0.  Instead of 0, we use IPSEC_*_ANY.
	 */
	((struct ipsecdoi_id_b *)new->v)->proto_id =
		ul_proto == IPSEC_ULPROTO_ANY ? 0 : ul_proto;
	port = ((struct sockaddr_in *)(laddr))->sin_port;
	((struct ipsecdoi_id_b *)new->v)->port =
		port == IPSEC_PORT_ANY ? 0 : port;
	memcpy(new->v + sizeof(struct ipsecdoi_id_b),
	       (caddr_t)&((struct sockaddr_in *)(laddr))->sin_addr,
	       len1);
	memcpy(new->v + sizeof(struct ipsecdoi_id_b) + len1,
	       (caddr_t)&((struct sockaddr_in *)haddr)->sin_addr,
	       len2);
	return new;
}


/*
 * create sockaddr structure from ID payload (buf).
 * buffers (saddr, prefixlen, ul_proto) must be allocated.
 * see, RFC2407 4.6.2.1
 */
int
ipsecdoi_id2sockaddr(buf, saddr, prefixlen, ul_proto)
	vchar_t *buf;
	struct sockaddr *saddr;
	u_int8_t *prefixlen;
	u_int16_t *ul_proto;
{
	struct ipsecdoi_id_b *id_b = NULL;
	u_int plen = 0;

	if (buf == NULL)
		return ISAKMP_INTERNAL_ERROR;

	id_b = (struct ipsecdoi_id_b *)buf->v;

	/*
	 * When a ID payload of subnet type with a IP address of full bit
	 * masked, it has to be processed as host address.
	 * e.g. below 2 type are same.
	 *      type = ipv6 subnet, data = 2001::1/128
	 *      type = ipv6 address, data = 2001::1
	 */
	switch (id_b->type) {
	case IPSECDOI_ID_IPV4_ADDR:
	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifndef __linux__
		saddr->sa_len = sizeof(struct sockaddr_in);
#endif
		saddr->sa_family = AF_INET;
		((struct sockaddr_in *)saddr)->sin_port =
			(id_b->port == 0
				? IPSEC_PORT_ANY
				: id_b->port);		/* see sockaddr2id() */
		memcpy(&((struct sockaddr_in *)saddr)->sin_addr,
			buf->v + sizeof(*id_b), sizeof(struct in_addr));
		break;
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR:
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#ifndef __linux__
		saddr->sa_len = sizeof(struct sockaddr_in6);
#endif
		saddr->sa_family = AF_INET6;
		((struct sockaddr_in6 *)saddr)->sin6_port =
			(id_b->port == 0
				? IPSEC_PORT_ANY
				: id_b->port);		/* see sockaddr2id() */
		memcpy(&((struct sockaddr_in6 *)saddr)->sin6_addr,
			buf->v + sizeof(*id_b), sizeof(struct in6_addr));
		((struct sockaddr_in6 *)saddr)->sin6_scope_id =
			(IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)saddr)->sin6_addr)
				? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
				: 0);

		break;
#endif
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"unsupported ID type %d\n", id_b->type);
		return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
	}

	/* get prefix length */
	switch (id_b->type) {
	case IPSECDOI_ID_IPV4_ADDR:
		plen = sizeof(struct in_addr) << 3;
		break;
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR:
		plen = sizeof(struct in6_addr) << 3;
		break;
#endif
	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#endif
	    {
		u_char *p;
		u_int max;
		int alen = sizeof(struct in_addr);

		switch (id_b->type) {
		case IPSECDOI_ID_IPV4_ADDR_SUBNET:
			alen = sizeof(struct in_addr);
			break;
#ifdef INET6
		case IPSECDOI_ID_IPV6_ADDR_SUBNET:
			alen = sizeof(struct in6_addr);
			break;
#endif
		}

		/* sanity check */
		if (buf->l < alen)
			return ISAKMP_INTERNAL_ERROR;

		/* get subnet mask length */
		plen = 0;
		max = alen <<3;

		p = (unsigned char *) buf->v
			+ sizeof(struct ipsecdoi_id_b)
			+ alen;

		for (; *p == 0xff; p++) {
			plen += 8;
			if (plen >= max)
				break;
		}

		if (plen < max) {
			u_int l = 0;
			u_char b = ~(*p);

			while (b) {
				b >>= 1;
				l++;
			}

			l = 8 - l;
			plen += l;
		}
	    }
		break;
	}

	*prefixlen = plen;
	*ul_proto = id_b->proto_id == 0
				? IPSEC_ULPROTO_ANY
				: id_b->proto_id;	/* see sockaddr2id() */

	return 0;
}

/*
 * make printable string from ID payload except of general header.
 */
char *
ipsecdoi_id2str(id)
	const vchar_t *id;
{
#define BUFLEN 512
	char * ret = NULL;
	int len = 0;
	char *dat;
	static char buf[BUFLEN];
	struct ipsecdoi_id_b *id_b = (struct ipsecdoi_id_b *)id->v;
	union sockaddr_any saddr;
	u_int plen = 0;

	switch (id_b->type) {
	case IPSECDOI_ID_IPV4_ADDR:
	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
	case IPSECDOI_ID_IPV4_ADDR_RANGE:

#ifndef __linux__
		saddr.sa.sa_len = sizeof(struct sockaddr_in);
#endif
		saddr.sa.sa_family = AF_INET;
		saddr.sin.sin_port = IPSEC_PORT_ANY;
		memcpy(&saddr.sin.sin_addr,
			id->v + sizeof(*id_b), sizeof(struct in_addr));
		break;
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR:
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
	case IPSECDOI_ID_IPV6_ADDR_RANGE:

#ifndef __linux__
		saddr.sa.sa_len = sizeof(struct sockaddr_in6);
#endif
		saddr.sa.sa_family = AF_INET6;
		saddr.sin6.sin6_port = IPSEC_PORT_ANY;
		memcpy(&saddr.sin6.sin6_addr,
			id->v + sizeof(*id_b), sizeof(struct in6_addr));
		saddr.sin6.sin6_scope_id =
			(IN6_IS_ADDR_LINKLOCAL(&saddr.sin6.sin6_addr)
				? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
				: 0);
		break;
#endif
	}

	switch (id_b->type) {
	case IPSECDOI_ID_IPV4_ADDR:
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR:
#endif
		len = snprintf( buf, BUFLEN, "%s", saddrwop2str(&saddr.sa));
		break;

	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#endif
	    {
		u_char *p;
		u_int max;
		int alen = sizeof(struct in_addr);

		switch (id_b->type) {
		case IPSECDOI_ID_IPV4_ADDR_SUBNET:
			alen = sizeof(struct in_addr);
			break;
#ifdef INET6
		case IPSECDOI_ID_IPV6_ADDR_SUBNET:
			alen = sizeof(struct in6_addr);
			break;
#endif
		}

		/* sanity check */
		if (id->l < alen) {
			len = 0;
			break;
		}

		/* get subnet mask length */
		plen = 0;
		max = alen <<3;

		p = (unsigned char *) id->v
			+ sizeof(struct ipsecdoi_id_b)
			+ alen;

		for (; *p == 0xff; p++) {
			plen += 8;
			if (plen >= max)
				break;
		}

		if (plen < max) {
			u_int l = 0;
			u_char b = ~(*p);

			while (b) {
				b >>= 1;
				l++;
			}

			l = 8 - l;
			plen += l;
		}

		len = snprintf( buf, BUFLEN, "%s/%i", saddrwop2str(&saddr.sa), plen);
	    }
		break;

	case IPSECDOI_ID_IPV4_ADDR_RANGE:

		len = snprintf( buf, BUFLEN, "%s-", saddrwop2str(&saddr.sa));

#ifndef __linux__
		saddr.sa.sa_len = sizeof(struct sockaddr_in);
#endif
		saddr.sa.sa_family = AF_INET;
		saddr.sin.sin_port = IPSEC_PORT_ANY;
		memcpy(&saddr.sin.sin_addr,
			id->v + sizeof(*id_b) + sizeof(struct in_addr),
			sizeof(struct in_addr));

		len += snprintf(buf + len, BUFLEN - len, "%s", saddrwop2str(&saddr.sa));
		break;

#ifdef INET6
	case IPSECDOI_ID_IPV6_ADDR_RANGE:
		len = snprintf( buf, BUFLEN, "%s-", saddrwop2str(&saddr.sa));

#ifndef __linux__
		saddr.sa.sa_len = sizeof(struct sockaddr_in6);
#endif
		saddr.sa.sa_family = AF_INET6;
		saddr.sin6.sin6_port = IPSEC_PORT_ANY;
		memcpy(&saddr.sin6.sin6_addr,
			id->v + sizeof(*id_b) + sizeof(struct in6_addr),
			sizeof(struct in6_addr));
		saddr.sin6.sin6_scope_id =
			(IN6_IS_ADDR_LINKLOCAL(&saddr.sin6.sin6_addr)
				? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
				: 0);

		len += snprintf(buf + len, BUFLEN - len, "%s", saddrwop2str(&saddr.sa));
		break;
#endif

	case IPSECDOI_ID_FQDN:
	case IPSECDOI_ID_USER_FQDN:
		len = id->l - sizeof(*id_b);
		if (len > BUFLEN)
			len = BUFLEN;
		memcpy(buf, id->v + sizeof(*id_b), len);
		break;

	case IPSECDOI_ID_DER_ASN1_DN:
	case IPSECDOI_ID_DER_ASN1_GN:
	{
		X509_NAME *xn = NULL;

		dat = id->v + sizeof(*id_b);
		len = id->l - sizeof(*id_b);

		if (d2i_X509_NAME(&xn, (void*) &dat, len) != NULL) {
			BIO *bio = BIO_new(BIO_s_mem());
			X509_NAME_print_ex(bio, xn, 0, 0);
			len = BIO_get_mem_data(bio, &dat);
			if (len > BUFLEN)
				len = BUFLEN;
			memcpy(buf,dat,len);
			BIO_free(bio);
			X509_NAME_free(xn);
		} else {
			plog(LLV_ERROR, LOCATION, NULL,
				"unable to extract asn1dn from id\n");

			len = sprintf(buf, "<ASN1-DN>");
		}

		break;
	}

	/* currently unhandled id types */
	case IPSECDOI_ID_KEY_ID:
		len = sprintf( buf, "<KEY-ID>");
		break;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"unknown ID type %d\n", id_b->type);
	}

	if (!len)
		len = sprintf( buf, "<?>");

	ret = racoon_malloc(len+1);
	if (ret != NULL) {
		memcpy(ret,buf,len);
		ret[len]=0;
	}

	return ret;
}

/*
 * set IPsec data attributes into a proposal.
 * NOTE: MUST called per a transform.
 */
int
ipsecdoi_t2satrns(t, pp, pr, tr)
	struct isakmp_pl_t *t;
	struct saprop *pp;
	struct saproto *pr;
	struct satrns *tr;
{
	struct isakmp_data *d, *prev;
	int flag, type;
	int error = -1;
	int life_t;
	int tlen;

	tr->trns_no = t->t_no;
	tr->trns_id = t->t_id;

	tlen = ntohs(t->h.len) - sizeof(*t);
	prev = (struct isakmp_data *)NULL;
	d = (struct isakmp_data *)(t + 1);

	/* default */
	life_t = IPSECDOI_ATTR_SA_LD_TYPE_DEFAULT;
	pp->lifetime = IPSECDOI_ATTR_SA_LD_SEC_DEFAULT;
	pp->lifebyte = 0;
	tr->authtype = IPSECDOI_ATTR_AUTH_NONE;

	while (tlen > 0) {

		type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
		flag = ntohs(d->type) & ISAKMP_GEN_MASK;

		plog(LLV_DEBUG, LOCATION, NULL,
			"type=%s, flag=0x%04x, lorv=%s\n",
			s_ipsecdoi_attr(type), flag,
			s_ipsecdoi_attr_v(type, ntohs(d->lorv)));

		switch (type) {
		case IPSECDOI_ATTR_SA_LD_TYPE:
		{
			int type = ntohs(d->lorv);
			switch (type) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				life_t = type;
				break;
			default:
				plog(LLV_WARNING, LOCATION, NULL,
					"invalid life duration type. "
					"use default\n");
				life_t = IPSECDOI_ATTR_SA_LD_TYPE_DEFAULT;
				break;
			}
			break;
		}
		case IPSECDOI_ATTR_SA_LD:
			if (prev == NULL
			 || (ntohs(prev->type) & ~ISAKMP_GEN_MASK) !=
					IPSECDOI_ATTR_SA_LD_TYPE) {
				plog(LLV_ERROR, LOCATION, NULL,
				    "life duration must follow ltype\n");
				break;
			}

		    {
			u_int32_t t;
			vchar_t *ld_buf = NULL;

			if (flag) {
				/* i.e. ISAKMP_GEN_TV */
				ld_buf = vmalloc(sizeof(d->lorv));
				if (ld_buf == NULL) {
					plog(LLV_ERROR, LOCATION, NULL,
					    "failed to get LD buffer.\n");
					goto end;
				}
				memcpy(ld_buf->v, &d->lorv, sizeof(d->lorv));
			} else {
				int len = ntohs(d->lorv);
				/* i.e. ISAKMP_GEN_TLV */
				ld_buf = vmalloc(len);
				if (ld_buf == NULL) {
					plog(LLV_ERROR, LOCATION, NULL,
					    "failed to get LD buffer.\n");
					goto end;
				}
				memcpy(ld_buf->v, d + 1, len);
			}
			switch (life_t) {
			case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
				t = ipsecdoi_set_ld(ld_buf);
				vfree(ld_buf);
				if (t == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid life duration.\n");
					goto end;
				}
				/* lifetime must be equal in a proposal. */
				if (pp->lifetime == IPSECDOI_ATTR_SA_LD_SEC_DEFAULT)
					pp->lifetime = t;
				else if (pp->lifetime != t) {
					plog(LLV_ERROR, LOCATION, NULL,
						"lifetime mismatched "
						"in a proposal, "
						"prev:%ld curr:%u.\n",
						(long)pp->lifetime, t);
					goto end;
				}
				break;
			case IPSECDOI_ATTR_SA_LD_TYPE_KB:
				t = ipsecdoi_set_ld(ld_buf);
				vfree(ld_buf);
				if (t == 0) {
					plog(LLV_ERROR, LOCATION, NULL,
						"invalid life duration.\n");
					goto end;
				}
				/* lifebyte must be equal in a proposal. */
				if (pp->lifebyte == 0)
					pp->lifebyte = t;
				else if (pp->lifebyte != t) {
					plog(LLV_ERROR, LOCATION, NULL,
						"lifebyte mismatched "
						"in a proposal, "
						"prev:%d curr:%u.\n",
						pp->lifebyte, t);
					goto end;
				}
				break;
			default:
				vfree(ld_buf);
				plog(LLV_ERROR, LOCATION, NULL,
					"invalid life type: %d\n", life_t);
				goto end;
			}
		    }
			break;

		case IPSECDOI_ATTR_GRP_DESC:
			/*
			 * RFC2407: 4.5 IPSEC Security Association Attributes
			 *   Specifies the Oakley Group to be used in a PFS QM
			 *   negotiation.  For a list of supported values, see
			 *   Appendix A of [IKE].
			 */
			if (pp->pfs_group == 0)
				pp->pfs_group = (u_int16_t)ntohs(d->lorv);
			else if (pp->pfs_group != (u_int16_t)ntohs(d->lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"pfs_group mismatched "
					"in a proposal.\n");
				goto end;
			}
			break;

		case IPSECDOI_ATTR_ENC_MODE:
			if (pr->encmode &&
			    pr->encmode != (u_int16_t)ntohs(d->lorv)) {
				plog(LLV_ERROR, LOCATION, NULL,
					"multiple encmode exist "
					"in a transform.\n");
				goto end;
			}
			pr->encmode = (u_int16_t)ntohs(d->lorv);
			break;

		case IPSECDOI_ATTR_AUTH:
			if (tr->authtype != IPSECDOI_ATTR_AUTH_NONE) {
				plog(LLV_ERROR, LOCATION, NULL,
					"multiple authtype exist "
					"in a transform.\n");
				goto end;
			}
			tr->authtype = (u_int16_t)ntohs(d->lorv);
			break;

		case IPSECDOI_ATTR_KEY_LENGTH:
			if (pr->proto_id != IPSECDOI_PROTO_IPSEC_ESP) {
				plog(LLV_ERROR, LOCATION, NULL,
					"key length defined but not ESP");
				goto end;
			}
			tr->encklen = ntohs(d->lorv);
			break;
#ifdef HAVE_SECCTX
		case IPSECDOI_ATTR_SECCTX:
		{
			int len = ntohs(d->lorv);
			memcpy(&pp->sctx, d + 1, len);
			pp->sctx.ctx_strlen = ntohs(pp->sctx.ctx_strlen);
			break;
		}
#endif /* HAVE_SECCTX */
		case IPSECDOI_ATTR_KEY_ROUNDS:
		case IPSECDOI_ATTR_COMP_DICT_SIZE:
		case IPSECDOI_ATTR_COMP_PRIVALG:
		default:
			break;
		}

		prev = d;
		if (flag) {
			tlen -= sizeof(*d);
			d = (struct isakmp_data *)((char *)d + sizeof(*d));
		} else {
			tlen -= (sizeof(*d) + ntohs(d->lorv));
			d = (struct isakmp_data *)((caddr_t)d + sizeof(*d) + ntohs(d->lorv));
		}
	}

	error = 0;
end:
	return error;
}

int
ipsecdoi_authalg2trnsid(alg)
	int alg;
{
	switch (alg) {
        case IPSECDOI_ATTR_AUTH_HMAC_MD5:
		return IPSECDOI_AH_MD5;
        case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
		return IPSECDOI_AH_SHA;
	case IPSECDOI_ATTR_AUTH_HMAC_SHA2_256:
		return IPSECDOI_AH_SHA256;
	case IPSECDOI_ATTR_AUTH_HMAC_SHA2_384:
		return IPSECDOI_AH_SHA384;
	case IPSECDOI_ATTR_AUTH_HMAC_SHA2_512:
		return IPSECDOI_AH_SHA512;
        case IPSECDOI_ATTR_AUTH_DES_MAC:
		return IPSECDOI_AH_DES;
	case IPSECDOI_ATTR_AUTH_KPDK:
		return IPSECDOI_AH_MD5;	/* XXX */
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid authentication algorithm:%d\n", alg);
	}
	return -1;
}

static int rm_idtype2doi[] = {
	255,				/* IDTYPE_UNDEFINED, 0 */
	IPSECDOI_ID_FQDN,		/* IDTYPE_FQDN, 1 */
	IPSECDOI_ID_USER_FQDN,		/* IDTYPE_USERFQDN, 2 */
	IPSECDOI_ID_KEY_ID,		/* IDTYPE_KEYID, 3 */
	255,    /*			   IDTYPE_ADDRESS, 4
		 * it expands into 4 types by another function. */
	IPSECDOI_ID_DER_ASN1_DN,	/* IDTYPE_ASN1DN, 5 */
};

/*
 * convert idtype to DOI value.
 * OUT	255  : NG
 *	other: converted.
 */
int
idtype2doi(idtype)
	int idtype;
{
	if (ARRAYLEN(rm_idtype2doi) > idtype)
		return rm_idtype2doi[idtype];
	return 255;
}

int
doi2idtype(doi)
	int doi;
{
	switch(doi) {
	case IPSECDOI_ID_FQDN:
		return(IDTYPE_FQDN);
	case IPSECDOI_ID_USER_FQDN:
		return(IDTYPE_USERFQDN);
	case IPSECDOI_ID_KEY_ID:
		return(IDTYPE_KEYID);
	case IPSECDOI_ID_DER_ASN1_DN:
		return(IDTYPE_ASN1DN);
	case IPSECDOI_ID_IPV4_ADDR:
	case IPSECDOI_ID_IPV4_ADDR_SUBNET:
	case IPSECDOI_ID_IPV6_ADDR:
	case IPSECDOI_ID_IPV6_ADDR_SUBNET:
		return(IDTYPE_ADDRESS);
	default:
		plog(LLV_WARNING, LOCATION, NULL,
			"Inproper idtype:%s in this function.\n",
			s_ipsecdoi_ident(doi));
		return(IDTYPE_ADDRESS);	/* XXX */
	}
	/*NOTREACHED*/
}
