root/crypto/dist/ipsec-tools/src/racoon/proposal.c
/*      $NetBSD: proposal.c,v 1.18 2025/03/07 15:55:29 christos Exp $   */

/* $Id: proposal.c,v 1.18 2025/03/07 15:55:29 christos 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(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(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(struct saprop *pp, struct saproto *new)
{
      new->next = pp->head;
      pp->head = new;

      return;
}

struct satrns *
newsatrns(void)
{
        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(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(struct ph1handle *ph1,
    const struct saprop *pp1,
    const struct saprop *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;

        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;

        for (;;) {
                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(uint16_t) &&
                            pr2->spisize == sizeof(uint32_t)) {
                                spisizematch = 1;
                        } else if (pr2->spisize == sizeof(uint16_t) &&
                                 pr1->spisize == sizeof(uint32_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(const struct saprop *pp1, const struct saprop *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(
    int proto_id,
    const struct satrns *tr1,
    const struct satrns *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;

        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(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(struct prop_pair *p0)
{
        struct prop_pair *p, *t;
        struct saprop *newpp;
        struct saproto *newpr;
        struct satrns *newtr;
        uint8_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 = (uint8_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(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(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(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(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(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(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(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(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(int pri, struct prop_pair *p)
{
        print_proppair0(pri, p, 1);
}

int
set_proposal_from_policy(struct ph2handle *iph2,
    struct secpolicy *sp_main,
    struct secpolicy *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;

                /*
                 * check if SA bundle ?
                 * nested SAs negotiation is NOT supported.
                 *       me +--- SA1 ---+ peer1
                 *       me +--- SA2 --------------+ peer2
                 */
                /* 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(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)
                {
                        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;

                        if (iph2->ph1->rmconf->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;
}