and added files
[bcm963xx.git] / userapps / opensource / ipsec-tools / src / racoon / proposal.c
diff --git a/userapps/opensource/ipsec-tools/src/racoon/proposal.c b/userapps/opensource/ipsec-tools/src/racoon/proposal.c
new file mode 100755 (executable)
index 0000000..9f30312
--- /dev/null
@@ -0,0 +1,1169 @@
+/* $Id: proposal.c,v 1.13 2004/09/13 14:09:19 ludvigm 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>
+#ifdef HAVE_NETINET6_IPSEC
+#  include <netinet6/ipsec.h>
+#else
+#  include <netinet/ipsec.h>
+#endif
+
+#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
+
+/* %%%
+ * 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;
+       }
+
+       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 (pr1->spisize == sizeof(u_int16_t) &&
+                                pr2->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) == 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");
+                       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)
+       int proto_id;
+       const struct satrns *tr1, *tr2;
+{
+       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;
+       }
+
+       /* XXX
+        * At this moment for interoperability, the responder obey
+        * the initiator.  It should be defined a notify message.
+        */
+       if (tr1->encklen > tr2->encklen) {
+               plog(LLV_WARNING, LOCATION, NULL,
+                       "less key length proposed, "
+                       "mine:%d peer:%d.  Use initiaotr's one.\n",
+                       tr2->encklen, tr1->encklen);
+               /* FALLTHRU */
+       }
+
+       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);
+                       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");
+                               goto err;
+                       }
+
+                       if (ipsecdoi_t2satrns(t->trns, newpp, newpr, newtr) < 0) {
+                               flushsaprop(newpp);
+                               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 = IPSEC_MODE_TRANSPORT; /* 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.
+        */
+       encmodesv = IPSEC_MODE_TRANSPORT;
+       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);
+               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))
+                               encmodesv += 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");
+                       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:
+       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;
+               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;
+
+               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) { 
+
+                       newpr = newsaproto();
+                       if (newpr == NULL) {
+                               plog(LLV_ERROR, LOCATION, NULL,
+                                   "failed to allocate saproto.\n");
+                               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;
+               }
+
+               if (set_satrnsbysainfo(newpr, iph2->sainfo) < 0) {
+                       plog(LLV_ERROR, LOCATION, NULL,
+                               "failed to get algorithms.\n");
+                       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);
+       free_proppair(pair);
+       return error;
+}