/*	$NetBSD: proposal.c,v 1.13.4.2 2008/07/22 13:25:42 vanhu Exp $	*/

/* $Id: proposal.c,v 1.13.4.2 2008/07/22 13:25:42 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/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/queue.h>

#include <netinet/in.h>
#include PATH_IPSEC_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

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

#include "policy.h"
#include "pfkey.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "ipsec_doi.h"
#include "algorithm.h"
#include "proposal.h"
#include "sainfo.h"
#include "localconf.h"
#include "remoteconf.h"
#include "oakley.h"
#include "handler.h"
#include "strnames.h"
#include "gcmalloc.h"
#ifdef ENABLE_NATT
#include "nattraversal.h"
#endif

static uint g_nextreqid = 1;

/* %%%
 * modules for ipsec sa spec
 */
struct saprop *
newsaprop()
{
	struct saprop *new;

	new = racoon_calloc(1, sizeof(*new));
	if (new == NULL)
		return NULL;

	return new;
}

struct saproto *
newsaproto()
{
	struct saproto *new;

	new = racoon_calloc(1, sizeof(*new));
	if (new == NULL)
		return NULL;

	return new;
}

/* set saprop to last part of the prop tree */
void
inssaprop(head, new)
	struct saprop **head;
	struct saprop *new;
{
	struct saprop *p;

	if (*head == NULL) {
		*head = new;
		return;
	}

	for (p = *head; p->next; p = p->next)
		;
	p->next = new;

	return;
}

/* set saproto to the end of the proto tree in saprop */
void
inssaproto(pp, new)
	struct saprop *pp;
	struct saproto *new;
{
	struct saproto *p;

	for (p = pp->head; p && p->next; p = p->next)
		;
	if (p == NULL)
		pp->head = new;
	else
		p->next = new;

	return;
}

/* set saproto to the top of the proto tree in saprop */
void
inssaprotorev(pp, new)
      struct saprop *pp;
      struct saproto *new;
{
      new->next = pp->head;
      pp->head = new;

      return;
}

struct satrns *
newsatrns()
{
	struct satrns *new;

	new = racoon_calloc(1, sizeof(*new));
	if (new == NULL)
		return NULL;

	return new;
}

/* set saproto to last part of the proto tree in saprop */
void
inssatrns(pr, new)
	struct saproto *pr;
	struct satrns *new;
{
	struct satrns *tr;

	for (tr = pr->head; tr && tr->next; tr = tr->next)
		;
	if (tr == NULL)
		pr->head = new;
	else
		tr->next = new;

	return;
}

/*
 * take a single match between saprop.  allocate a new proposal and return it
 * for future use (like picking single proposal from a bundle).
 *	pp1: peer's proposal.
 *	pp2: my proposal.
 * NOTE: In the case of initiator, must be ensured that there is no
 * modification of the proposal by calling cmp_aproppair_i() before
 * this function.
 * XXX cannot understand the comment!
 */
struct saprop *
cmpsaprop_alloc(ph1, pp1, pp2, side)
	struct ph1handle *ph1;
	const struct saprop *pp1, *pp2;
	int side;
{
	struct saprop *newpp = NULL;
	struct saproto *pr1, *pr2, *newpr = NULL;
	struct satrns *tr1, *tr2, *newtr;
	const int ordermatters = 0;
	int npr1, npr2;
	int spisizematch;

	newpp = newsaprop();
	if (newpp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate saprop.\n");
		return NULL;
	}
	newpp->prop_no = pp1->prop_no;

	/* see proposal.h about lifetime/key length and PFS selection. */

	/* check time/bytes lifetime and PFS */
	switch (ph1->rmconf->pcheck_level) {
	case PROP_CHECK_OBEY:
		newpp->lifetime = pp1->lifetime;
		newpp->lifebyte = pp1->lifebyte;
		newpp->pfs_group = pp1->pfs_group;
		break;

	case PROP_CHECK_STRICT:
		if (pp1->lifetime > pp2->lifetime) {
			plog(LLV_ERROR, LOCATION, NULL,
				"long lifetime proposed: "
				"my:%d peer:%d\n",
				(int)pp2->lifetime, (int)pp1->lifetime);
			goto err;
		}
		if (pp1->lifebyte > pp2->lifebyte) {
			plog(LLV_ERROR, LOCATION, NULL,
				"long lifebyte proposed: "
				"my:%d peer:%d\n",
				pp2->lifebyte, pp1->lifebyte);
			goto err;
		}
		newpp->lifetime = pp1->lifetime;
		newpp->lifebyte = pp1->lifebyte;

    prop_pfs_check:
		if (pp2->pfs_group != 0 && pp1->pfs_group != pp2->pfs_group) {
			plog(LLV_ERROR, LOCATION, NULL,
				"pfs group mismatched: "
				"my:%d peer:%d\n",
				pp2->pfs_group, pp1->pfs_group);
			goto err;
		}
		newpp->pfs_group = pp1->pfs_group;
		break;

	case PROP_CHECK_CLAIM:
		/* lifetime */
		if (pp1->lifetime <= pp2->lifetime) {
			newpp->lifetime = pp1->lifetime;
		} else {
			newpp->lifetime = pp2->lifetime;
			newpp->claim |= IPSECDOI_ATTR_SA_LD_TYPE_SEC;
			plog(LLV_NOTIFY, LOCATION, NULL,
				"use own lifetime: "
				"my:%d peer:%d\n",
				(int)pp2->lifetime, (int)pp1->lifetime);
		}

		/* lifebyte */
		if (pp1->lifebyte > pp2->lifebyte) {
			newpp->lifebyte = pp2->lifebyte;
			newpp->claim |= IPSECDOI_ATTR_SA_LD_TYPE_SEC;
			plog(LLV_NOTIFY, LOCATION, NULL,
				"use own lifebyte: "
				"my:%d peer:%d\n",
				pp2->lifebyte, pp1->lifebyte);
		}
		newpp->lifebyte = pp1->lifebyte;

    		goto prop_pfs_check;
		break;

	case PROP_CHECK_EXACT:
		if (pp1->lifetime != pp2->lifetime) {
			plog(LLV_ERROR, LOCATION, NULL,
				"lifetime mismatched: "
				"my:%d peer:%d\n",
				(int)pp2->lifetime, (int)pp1->lifetime);
			goto err;
		}

		if (pp1->lifebyte != pp2->lifebyte) {
			plog(LLV_ERROR, LOCATION, NULL,
				"lifebyte mismatched: "
				"my:%d peer:%d\n",
				pp2->lifebyte, pp1->lifebyte);
			goto err;
		}
		if (pp1->pfs_group != pp2->pfs_group) {
			plog(LLV_ERROR, LOCATION, NULL,
				"pfs group mismatched: "
				"my:%d peer:%d\n",
				pp2->pfs_group, pp1->pfs_group);
			goto err;
		}
		newpp->lifetime = pp1->lifetime;
		newpp->lifebyte = pp1->lifebyte;
		newpp->pfs_group = pp1->pfs_group;
		break;

	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"invalid pcheck_level why?.\n");
		goto err;
	}

#ifdef HAVE_SECCTX
	/* check the security_context properties.
	 * It is possible for one side to have a security context
	 * and the other side doesn't. If so, this is an error.
	 */

	if (*pp1->sctx.ctx_str && !(*pp2->sctx.ctx_str)) {
		plog(LLV_ERROR, LOCATION, NULL,
		     "My proposal missing security context\n");
		goto err;
	}
	if (!(*pp1->sctx.ctx_str) && *pp2->sctx.ctx_str) {
		plog(LLV_ERROR, LOCATION, NULL, 
		     "Peer is missing security context\n");
		goto err;
	}

	if (*pp1->sctx.ctx_str && *pp2->sctx.ctx_str) {
		if (pp1->sctx.ctx_doi == pp2->sctx.ctx_doi)
			newpp->sctx.ctx_doi = pp1->sctx.ctx_doi;
		else {
			plog(LLV_ERROR, LOCATION, NULL, 
			     "sec doi mismatched: my:%d peer:%d\n",
			     pp2->sctx.ctx_doi, pp1->sctx.ctx_doi);
			     goto err;
		}

		if (pp1->sctx.ctx_alg == pp2->sctx.ctx_alg)
			newpp->sctx.ctx_alg = pp1->sctx.ctx_alg;
		else {
			plog(LLV_ERROR, LOCATION, NULL,
			     "sec alg mismatched: my:%d peer:%d\n",
			     pp2->sctx.ctx_alg, pp1->sctx.ctx_alg);
			goto err;
		}

		if ((pp1->sctx.ctx_strlen != pp2->sctx.ctx_strlen) ||
		     memcmp(pp1->sctx.ctx_str, pp2->sctx.ctx_str,
		     pp1->sctx.ctx_strlen) != 0) {
			plog(LLV_ERROR, LOCATION, NULL,
			     "sec ctx string mismatched: my:%s peer:%s\n",
			     pp2->sctx.ctx_str, pp1->sctx.ctx_str);
				goto err;
		} else {
			newpp->sctx.ctx_strlen = pp1->sctx.ctx_strlen;
			memcpy(newpp->sctx.ctx_str, pp1->sctx.ctx_str,
				pp1->sctx.ctx_strlen);
		}
	}
#endif /* HAVE_SECCTX */

	npr1 = npr2 = 0;
	for (pr1 = pp1->head; pr1; pr1 = pr1->next)
		npr1++;
	for (pr2 = pp2->head; pr2; pr2 = pr2->next)
		npr2++;
	if (npr1 != npr2)
		goto err;

	/* check protocol order */
	pr1 = pp1->head;
	pr2 = pp2->head;

	while (1) {
		if (!ordermatters) {
			/*
			 * XXX does not work if we have multiple proposals
			 * with the same proto_id
			 */
			switch (side) {
			case RESPONDER:
				if (!pr2)
					break;
				for (pr1 = pp1->head; pr1; pr1 = pr1->next) {
					if (pr1->proto_id == pr2->proto_id)
						break;
				}
				break;
			case INITIATOR:
				if (!pr1)
					break;
				for (pr2 = pp2->head; pr2; pr2 = pr2->next) {
					if (pr2->proto_id == pr1->proto_id)
						break;
				}
				break;
			}
		}
		if (!pr1 || !pr2)
			break;

		if (pr1->proto_id != pr2->proto_id) {
			plog(LLV_ERROR, LOCATION, NULL,
				"proto_id mismatched: "
				"my:%s peer:%s\n",
				s_ipsecdoi_proto(pr2->proto_id),
				s_ipsecdoi_proto(pr1->proto_id));
			goto err;
		}
		spisizematch = 0;
		if (pr1->spisize == pr2->spisize)
			spisizematch = 1;
		else if (pr1->proto_id == IPSECDOI_PROTO_IPCOMP) {
			/*
			 * draft-shacham-ippcp-rfc2393bis-05.txt:
			 * need to accept 16bit and 32bit SPI (CPI) for IPComp.
			 */
			if (pr1->spisize == sizeof(u_int16_t) &&
			    pr2->spisize == sizeof(u_int32_t)) {
				spisizematch = 1;
			} else if (pr2->spisize == sizeof(u_int16_t) &&
				 pr1->spisize == sizeof(u_int32_t)) {
				spisizematch = 1;
			}
			if (spisizematch) {
				plog(LLV_ERROR, LOCATION, NULL,
				    "IPComp SPI size promoted "
				    "from 16bit to 32bit\n");
			}
		}
		if (!spisizematch) {
			plog(LLV_ERROR, LOCATION, NULL,
				"spisize mismatched: "
				"my:%d peer:%d\n",
				(int)pr2->spisize, (int)pr1->spisize);
			goto err;
		}

#ifdef ENABLE_NATT
		if ((ph1->natt_flags & NAT_DETECTED) && 
		    natt_udp_encap (pr2->encmode))
		{
			plog(LLV_INFO, LOCATION, NULL, "Adjusting my encmode %s->%s\n",
			     s_ipsecdoi_encmode(pr2->encmode),
			     s_ipsecdoi_encmode(pr2->encmode - ph1->natt_options->mode_udp_diff));
			pr2->encmode -= ph1->natt_options->mode_udp_diff;
			pr2->udp_encap = 1;
		}

		if ((ph1->natt_flags & NAT_DETECTED) &&
		    natt_udp_encap (pr1->encmode))
		{
			plog(LLV_INFO, LOCATION, NULL, "Adjusting peer's encmode %s(%d)->%s(%d)\n",
			     s_ipsecdoi_encmode(pr1->encmode),
			     pr1->encmode,
			     s_ipsecdoi_encmode(pr1->encmode - ph1->natt_options->mode_udp_diff),
			     pr1->encmode - ph1->natt_options->mode_udp_diff);
			pr1->encmode -= ph1->natt_options->mode_udp_diff;
			pr1->udp_encap = 1;
		}
#endif

		if (pr1->encmode != pr2->encmode) {
			plog(LLV_ERROR, LOCATION, NULL,
				"encmode mismatched: "
				"my:%s peer:%s\n",
				s_ipsecdoi_encmode(pr2->encmode),
				s_ipsecdoi_encmode(pr1->encmode));
			goto err;
		}

		for (tr1 = pr1->head; tr1; tr1 = tr1->next) {
			for (tr2 = pr2->head; tr2; tr2 = tr2->next) {
				if (cmpsatrns(pr1->proto_id, tr1, tr2, ph1->rmconf->pcheck_level) == 0)
					goto found;
			}
		}

		goto err;

	    found:
		newpr = newsaproto();
		if (newpr == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to allocate saproto.\n");
			goto err;
		}
		newpr->proto_id = pr1->proto_id;
		newpr->spisize = pr1->spisize;
		newpr->encmode = pr1->encmode;
		newpr->spi = pr2->spi;		/* copy my SPI */
		newpr->spi_p = pr1->spi;	/* copy peer's SPI */
		newpr->reqid_in = pr2->reqid_in;
		newpr->reqid_out = pr2->reqid_out;
#ifdef ENABLE_NATT
		newpr->udp_encap = pr1->udp_encap | pr2->udp_encap;
#endif

		newtr = newsatrns();
		if (newtr == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to allocate satrns.\n");
			racoon_free(newpr);
			goto err;
		}
		newtr->trns_no = tr1->trns_no;
		newtr->trns_id = tr1->trns_id;
		newtr->encklen = tr1->encklen;
		newtr->authtype = tr1->authtype;

		inssatrns(newpr, newtr);
		inssaproto(newpp, newpr);

		pr1 = pr1->next;
		pr2 = pr2->next;
	}

	/* XXX should check if we have visited all items or not */
	if (!ordermatters) {
		switch (side) {
		case RESPONDER:
			if (!pr2)
				pr1 = NULL;
			break;
		case INITIATOR:
			if (!pr1)
				pr2 = NULL;
			break;
		}
	}

	/* should be matched all protocols in a proposal */
	if (pr1 != NULL || pr2 != NULL)
		goto err;

	return newpp;

err:
	flushsaprop(newpp);
	return NULL;
}

/* take a single match between saprop.  returns 0 if pp1 equals to pp2. */
int
cmpsaprop(pp1, pp2)
	const struct saprop *pp1, *pp2;
{
	if (pp1->pfs_group != pp2->pfs_group) {
		plog(LLV_WARNING, LOCATION, NULL,
			"pfs_group mismatch. mine:%d peer:%d\n",
			pp1->pfs_group, pp2->pfs_group);
		/* FALLTHRU */
	}

	if (pp1->lifetime > pp2->lifetime) {
		plog(LLV_WARNING, LOCATION, NULL,
			"less lifetime proposed. mine:%d peer:%d\n",
			(int)pp1->lifetime, (int)pp2->lifetime);
		/* FALLTHRU */
	}
	if (pp1->lifebyte > pp2->lifebyte) {
		plog(LLV_WARNING, LOCATION, NULL,
			"less lifebyte proposed. mine:%d peer:%d\n",
			pp1->lifebyte, pp2->lifebyte);
		/* FALLTHRU */
	}

	return 0;
}

/*
 * take a single match between satrns.  returns 0 if tr1 equals to tr2.
 * tr1: peer's satrns
 * tr2: my satrns
 */
int
cmpsatrns(proto_id, tr1, tr2, check_level)
	int proto_id;
	const struct satrns *tr1, *tr2;
	int check_level;
{
	if (tr1->trns_id != tr2->trns_id) {
		plog(LLV_WARNING, LOCATION, NULL,
			"trns_id mismatched: "
			"my:%s peer:%s\n",
			s_ipsecdoi_trns(proto_id, tr2->trns_id),
			s_ipsecdoi_trns(proto_id, tr1->trns_id));
		return 1;
	}

	if (tr1->authtype != tr2->authtype) {
		plog(LLV_WARNING, LOCATION, NULL,
			"authtype mismatched: "
			"my:%s peer:%s\n",
			s_ipsecdoi_attr_v(IPSECDOI_ATTR_AUTH, tr2->authtype),
			s_ipsecdoi_attr_v(IPSECDOI_ATTR_AUTH, tr1->authtype));
		return 1;
	}

	/* Check key length regarding checkmode
	 * XXX Shall we send some kind of notify message when key length rejected ?
	 */
	switch(check_level){
	case PROP_CHECK_OBEY:
		return 0;
		break;

	case PROP_CHECK_STRICT:
		/* FALLTHROUGH */
	case PROP_CHECK_CLAIM:
		if (tr1->encklen < tr2->encklen) {
		plog(LLV_WARNING, LOCATION, NULL,
				 "low key length proposed, "
				 "mine:%d peer:%d.\n",
			tr2->encklen, tr1->encklen);
			return 1;
		}
		break;
	case PROP_CHECK_EXACT:
		if (tr1->encklen != tr2->encklen) {
			plog(LLV_WARNING, LOCATION, NULL,
				 "key length mismatched, "
				 "mine:%d peer:%d.\n",
				 tr2->encklen, tr1->encklen);
			return 1;
		}
		break;
	}

	return 0;
}

int
set_satrnsbysainfo(pr, sainfo)
	struct saproto *pr;
	struct sainfo *sainfo;
{
	struct sainfoalg *a, *b;
	struct satrns *newtr;
	int t;

	switch (pr->proto_id) {
	case IPSECDOI_PROTO_IPSEC_AH:
		if (sainfo->algs[algclass_ipsec_auth] == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"no auth algorithm found\n");
			goto err;
		}
		t = 1;
		for (a = sainfo->algs[algclass_ipsec_auth]; a; a = a->next) {

			if (a->alg == IPSECDOI_ATTR_AUTH_NONE)
				continue;
				
			/* allocate satrns */
			newtr = newsatrns();
			if (newtr == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to allocate satrns.\n");
				goto err;
			}

			newtr->trns_no = t++;
			newtr->trns_id = ipsecdoi_authalg2trnsid(a->alg);
			newtr->authtype = a->alg;

			inssatrns(pr, newtr);
		}
		break;
	case IPSECDOI_PROTO_IPSEC_ESP:
		if (sainfo->algs[algclass_ipsec_enc] == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"no encryption algorithm found\n");
			goto err;
		}
		t = 1;
		for (a = sainfo->algs[algclass_ipsec_enc]; a; a = a->next) {
			for (b = sainfo->algs[algclass_ipsec_auth]; b; b = b->next) {
				/* allocate satrns */
				newtr = newsatrns();
				if (newtr == NULL) {
					plog(LLV_ERROR, LOCATION, NULL,
						"failed to allocate satrns.\n");
					goto err;
				}

				newtr->trns_no = t++;
				newtr->trns_id = a->alg;
				newtr->encklen = a->encklen;
				newtr->authtype = b->alg;

				inssatrns(pr, newtr);
			}
		}
		break;
	case IPSECDOI_PROTO_IPCOMP:
		if (sainfo->algs[algclass_ipsec_comp] == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"no ipcomp algorithm found\n");
			goto err;
		}
		t = 1;
		for (a = sainfo->algs[algclass_ipsec_comp]; a; a = a->next) {

			/* allocate satrns */
			newtr = newsatrns();
			if (newtr == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to allocate satrns.\n");
				goto err;
			}

			newtr->trns_no = t++;
			newtr->trns_id = a->alg;
			newtr->authtype = IPSECDOI_ATTR_AUTH_NONE; /*no auth*/

			inssatrns(pr, newtr);
		}
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL,
			"unknown proto_id (%d).\n", pr->proto_id);
		goto err;
	}

	/* no proposal found */
	if (pr->head == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "no algorithms found.\n");
		return -1;
	}

	return 0;

err:
	flushsatrns(pr->head);
	return -1;
}

struct saprop *
aproppair2saprop(p0)
	struct prop_pair *p0;
{
	struct prop_pair *p, *t;
	struct saprop *newpp;
	struct saproto *newpr;
	struct satrns *newtr;
	u_int8_t *spi;

	if (p0 == NULL)
		return NULL;

	/* allocate ipsec a sa proposal */
	newpp = newsaprop();
	if (newpp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate saprop.\n");
		return NULL;
	}
	newpp->prop_no = p0->prop->p_no;
	/* lifetime & lifebyte must be updated later */

	for (p = p0; p; p = p->next) {

		/* allocate ipsec sa protocol */
		newpr = newsaproto();
		if (newpr == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to allocate saproto.\n");
			goto err;
		}

		/* check spi size */
		/* XXX should be handled isakmp cookie */
		if (sizeof(newpr->spi) < p->prop->spi_size) {
			plog(LLV_ERROR, LOCATION, NULL,
				"invalid spi size %d.\n", p->prop->spi_size);
			racoon_free(newpr);
			goto err;
		}

		/*
		 * XXX SPI bits are left-filled, for use with IPComp.
		 * we should be switching to variable-length spi field...
		 */
		newpr->proto_id = p->prop->proto_id;
		newpr->spisize = p->prop->spi_size;
		memset(&newpr->spi, 0, sizeof(newpr->spi));
		spi = (u_int8_t *)&newpr->spi;
		spi += sizeof(newpr->spi);
		spi -= p->prop->spi_size;
		memcpy(spi, p->prop + 1, p->prop->spi_size);
		newpr->reqid_in = 0;
		newpr->reqid_out = 0;

		for (t = p; t; t = t->tnext) {

			plog(LLV_DEBUG, LOCATION, NULL,
				"prop#=%d prot-id=%s spi-size=%d "
				"#trns=%d trns#=%d trns-id=%s\n",
				t->prop->p_no,
				s_ipsecdoi_proto(t->prop->proto_id),
				t->prop->spi_size, t->prop->num_t,
				t->trns->t_no,
				s_ipsecdoi_trns(t->prop->proto_id,
				t->trns->t_id));

			/* allocate ipsec sa transform */
			newtr = newsatrns();
			if (newtr == NULL) {
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to allocate satrns.\n");
				racoon_free(newpr);
				goto err;
			}

			if (ipsecdoi_t2satrns(t->trns, 
			    newpp, newpr, newtr) < 0) {
				flushsaprop(newpp);
				racoon_free(newtr);
				racoon_free(newpr);
				return NULL;
			}

			inssatrns(newpr, newtr);
		}

		/*
		 * If the peer does not specify encryption mode, use 
		 * transport mode by default.  This is to conform to
		 * draft-shacham-ippcp-rfc2393bis-08.txt (explicitly specifies
		 * that unspecified == transport), as well as RFC2407
		 * (unspecified == implementation dependent default).
		 */
		if (newpr->encmode == 0)
			newpr->encmode = IPSECDOI_ATTR_ENC_MODE_TRNS;

		inssaproto(newpp, newpr);
	}

	return newpp;

err:
	flushsaprop(newpp);
	return NULL;
}

void
flushsaprop(head)
	struct saprop *head;
{
	struct saprop *p, *save;

	for (p = head; p != NULL; p = save) {
		save = p->next;
		flushsaproto(p->head);
		racoon_free(p);
	}

	return;
}

void
flushsaproto(head)
	struct saproto *head;
{
	struct saproto *p, *save;

	for (p = head; p != NULL; p = save) {
		save = p->next;
		flushsatrns(p->head);
		vfree(p->keymat);
		vfree(p->keymat_p);
		racoon_free(p);
	}

	return;
}

void
flushsatrns(head)
	struct satrns *head;
{
	struct satrns *p, *save;

	for (p = head; p != NULL; p = save) {
		save = p->next;
		racoon_free(p);
	}

	return;
}

/*
 * print multiple proposals
 */
void
printsaprop(pri, pp)
	const int pri;
	const struct saprop *pp;
{
	const struct saprop *p;

	if (pp == NULL) {
		plog(pri, LOCATION, NULL, "(null)");
		return;
	}

	for (p = pp; p; p = p->next) {
		printsaprop0(pri, p);
	}

	return;
}

/*
 * print one proposal.
 */
void
printsaprop0(pri, pp)
	int pri;
	const struct saprop *pp;
{
	const struct saproto *p;

	if (pp == NULL)
		return;

	for (p = pp->head; p; p = p->next) {
		printsaproto(pri, p);
	}

	return;
}

void
printsaproto(pri, pr)
	const int pri;
	const struct saproto *pr;
{
	struct satrns *tr;

	if (pr == NULL)
		return;

	plog(pri, LOCATION, NULL,
		" (proto_id=%s spisize=%d spi=%08lx spi_p=%08lx "
		"encmode=%s reqid=%d:%d)\n",
		s_ipsecdoi_proto(pr->proto_id),
		(int)pr->spisize,
		(unsigned long)ntohl(pr->spi),
		(unsigned long)ntohl(pr->spi_p),
		s_ipsecdoi_attr_v(IPSECDOI_ATTR_ENC_MODE, pr->encmode),
		(int)pr->reqid_in, (int)pr->reqid_out);

	for (tr = pr->head; tr; tr = tr->next) {
		printsatrns(pri, pr->proto_id, tr);
	}

	return;
}

void
printsatrns(pri, proto_id, tr)
	const int pri;
	const int proto_id;
	const struct satrns *tr;
{
	if (tr == NULL)
		return;

	switch (proto_id) {
	case IPSECDOI_PROTO_IPSEC_AH:
		plog(pri, LOCATION, NULL,
			"  (trns_id=%s authtype=%s)\n",
			s_ipsecdoi_trns(proto_id, tr->trns_id),
			s_ipsecdoi_attr_v(IPSECDOI_ATTR_AUTH, tr->authtype));
		break;
	case IPSECDOI_PROTO_IPSEC_ESP:
		plog(pri, LOCATION, NULL,
			"  (trns_id=%s encklen=%d authtype=%s)\n",
			s_ipsecdoi_trns(proto_id, tr->trns_id),
			tr->encklen,
			s_ipsecdoi_attr_v(IPSECDOI_ATTR_AUTH, tr->authtype));
		break;
	case IPSECDOI_PROTO_IPCOMP:
		plog(pri, LOCATION, NULL,
			"  (trns_id=%s)\n",
			s_ipsecdoi_trns(proto_id, tr->trns_id));
		break;
	default:
		plog(pri, LOCATION, NULL,
			"(unknown proto_id %d)\n", proto_id);
	}

	return;
}

void
print_proppair0(pri, p, level)
	int pri; 
	struct prop_pair *p;
	int level;
{
	char spc[21];

	memset(spc, ' ', sizeof(spc));
	spc[sizeof(spc) - 1] = '\0';
	if (level < 20) {
		spc[level] = '\0';
	}

	plog(pri, LOCATION, NULL,
		"%s%p: next=%p tnext=%p\n", spc, p, p->next, p->tnext);
	if (p->next)
		print_proppair0(pri, p->next, level + 1);
	if (p->tnext)
		print_proppair0(pri, p->tnext, level + 1);
}

void
print_proppair(pri, p)
	int pri;
	struct prop_pair *p;
{
	print_proppair0(pri, p, 1);
}

int
set_proposal_from_policy(iph2, sp_main, sp_sub)
	struct ph2handle *iph2;
	struct secpolicy *sp_main, *sp_sub;
{
	struct saprop *newpp;
	struct ipsecrequest *req;
	int encmodesv = IPSECDOI_ATTR_ENC_MODE_TRNS; /* use only when complex_bundle */

	newpp = newsaprop();
	if (newpp == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to allocate saprop.\n");
		goto err;
	}
	newpp->prop_no = 1;
	newpp->lifetime = iph2->sainfo->lifetime;
	newpp->lifebyte = iph2->sainfo->lifebyte;
	newpp->pfs_group = iph2->sainfo->pfs_group;

	if (lcconf->complex_bundle)
		goto skip1;

	/*
	 * decide the encryption mode of this SA bundle.
	 * the mode becomes tunnel mode when there is even one policy
	 * of tunnel mode in the SPD.  otherwise the mode becomes
	 * transport mode.
	 */
	for (req = sp_main->req; req; req = req->next) {
		if (req->saidx.mode == IPSEC_MODE_TUNNEL) {
			encmodesv = pfkey2ipsecdoi_mode(req->saidx.mode);
#ifdef ENABLE_NATT
			if (iph2->ph1 && (iph2->ph1->natt_flags & NAT_DETECTED))
				encmodesv += iph2->ph1->natt_options->mode_udp_diff;
#endif
			break;
		}
	}

    skip1:
	for (req = sp_main->req; req; req = req->next) {
		struct saproto *newpr;
		caddr_t paddr = NULL;

		/*
		 * check if SA bundle ?
		 * nested SAs negotiation is NOT supported.
		 *       me +--- SA1 ---+ peer1
		 *       me +--- SA2 --------------+ peer2
		 */
#ifdef __linux__
		if (req->saidx.src.ss_family && req->saidx.dst.ss_family) {
#else
		if (req->saidx.src.ss_len && req->saidx.dst.ss_len) {
#endif
			/* check the end of ip addresses of SA */
			if (iph2->side == INITIATOR)
				paddr = (caddr_t)&req->saidx.dst;
			else
				paddr = (caddr_t)&req->saidx.src;
		}

		/* allocate ipsec sa protocol */
		newpr = newsaproto();
		if (newpr == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to allocate saproto.\n");
			goto err;
		}

		newpr->proto_id = ipproto2doi(req->saidx.proto);
		if (newpr->proto_id == IPSECDOI_PROTO_IPCOMP)
			newpr->spisize = 2;
		else
			newpr->spisize = 4;
		if (lcconf->complex_bundle) {
			newpr->encmode = pfkey2ipsecdoi_mode(req->saidx.mode);
#ifdef ENABLE_NATT
			if (iph2->ph1 && (iph2->ph1->natt_flags & NAT_DETECTED))
				newpr->encmode += 
				    iph2->ph1->natt_options->mode_udp_diff;
#endif
		}
		else
			newpr->encmode = encmodesv;

		if (iph2->side == INITIATOR)
			newpr->reqid_out = req->saidx.reqid;
		else
			newpr->reqid_in = req->saidx.reqid;

		if (set_satrnsbysainfo(newpr, iph2->sainfo) < 0) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to get algorithms.\n");
			racoon_free(newpr);
			goto err;
		}

		/* set new saproto */
		inssaprotorev(newpp, newpr);
	}

	/* get reqid_in from inbound policy */
	if (sp_sub) {
		struct saproto *pr;

		req = sp_sub->req;
		pr = newpp->head;
		while (req && pr) {
			if (iph2->side == INITIATOR)
				pr->reqid_in = req->saidx.reqid;
			else
				pr->reqid_out = req->saidx.reqid;
			pr = pr->next;
			req = req->next;
		}
		if (pr || req) {
			plog(LLV_NOTIFY, LOCATION, NULL,
				"There is a difference "
				"between the in/out bound policies in SPD.\n");
		}
	}

	iph2->proposal = newpp;

	printsaprop0(LLV_DEBUG, newpp);

	return 0;
err:
	flushsaprop(newpp);
	return -1;
}

/*
 * generate a policy from peer's proposal.
 * this function unconditionally choices first proposal in SA payload
 * passed by peer.
 */
int
set_proposal_from_proposal(iph2)
	struct ph2handle *iph2;
{
        struct saprop *newpp = NULL, *pp0, *pp_peer = NULL;
	struct saproto *newpr = NULL, *pr;
	struct prop_pair **pair;
	int error = -1;
	int i;

	/* get proposal pair */
	pair = get_proppair(iph2->sa, IPSECDOI_TYPE_PH2);
	if (pair == NULL)
		goto end;

	/*
	 * make my proposal according as the client proposal.
	 * XXX assumed there is only one proposal even if it's the SA bundle.
	 */
        for (i = 0; i < MAXPROPPAIRLEN; i++) {
                if (pair[i] == NULL)
                        continue;

		if (pp_peer != NULL)
			flushsaprop(pp_peer);

		pp_peer = aproppair2saprop(pair[i]);
		if (pp_peer == NULL)
			goto end;

		pp0 = newsaprop();
		if (pp0 == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"failed to allocate saprop.\n");
			goto end;
		}
		pp0->prop_no = 1;
		pp0->lifetime = iph2->sainfo->lifetime;
		pp0->lifebyte = iph2->sainfo->lifebyte;
		pp0->pfs_group = iph2->sainfo->pfs_group;

#ifdef HAVE_SECCTX
		if (*pp_peer->sctx.ctx_str) {
			pp0->sctx.ctx_doi = pp_peer->sctx.ctx_doi;
			pp0->sctx.ctx_alg = pp_peer->sctx.ctx_alg;
			pp0->sctx.ctx_strlen = pp_peer->sctx.ctx_strlen;
			memcpy(pp0->sctx.ctx_str, pp_peer->sctx.ctx_str,
			       pp_peer->sctx.ctx_strlen);
		}
#endif /* HAVE_SECCTX */

		if (pp_peer->next != NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
				"pp_peer is inconsistency, ignore it.\n");
			/*FALLTHROUGH*/
		}

		for (pr = pp_peer->head; pr; pr = pr->next)
		{
			struct remoteconf *conf;

			newpr = newsaproto();
			if (newpr == NULL)
			{
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to allocate saproto.\n");
				racoon_free(pp0);
				goto end;
			}
			newpr->proto_id = pr->proto_id;
			newpr->spisize = pr->spisize;
			newpr->encmode = pr->encmode;
			newpr->spi = 0;
			newpr->spi_p = pr->spi;     /* copy peer's SPI */
			newpr->reqid_in = 0;
			newpr->reqid_out = 0;

			conf = getrmconf(iph2->dst);
			if (conf != NULL &&
				conf->gen_policy == GENERATE_POLICY_UNIQUE){
				newpr->reqid_in = g_nextreqid ;
				newpr->reqid_out = g_nextreqid ++;
				/* 
				 * XXX there is a (very limited) 
				 * risk of reusing the same reqid
				 * as another SP entry for the same peer
				 */
				if(g_nextreqid >= IPSEC_MANUAL_REQID_MAX)
					g_nextreqid = 1;
			}else{
				newpr->reqid_in = 0;
				newpr->reqid_out = 0;
			}
 
			if (set_satrnsbysainfo(newpr, iph2->sainfo) < 0)
			{
				plog(LLV_ERROR, LOCATION, NULL,
					"failed to get algorithms.\n");
				racoon_free(newpr);
				racoon_free(pp0);
				goto end;
			}
			inssaproto(pp0, newpr);
		}

		inssaprop(&newpp, pp0);
        }

	plog(LLV_DEBUG, LOCATION, NULL, "make a proposal from peer's:\n");
	printsaprop0(LLV_DEBUG, newpp);  

	iph2->proposal = newpp;

	error = 0;

end:
	if (error && newpp)
		flushsaprop(newpp);

	if (pp_peer)
		flushsaprop(pp_peer);
	if (pair)
		free_proppair(pair);
	return error;
}