root/crypto/dist/ipsec-tools/src/racoon/isakmp.c
/*      $NetBSD: isakmp.c,v 1.81 2025/03/08 16:39:08 christos Exp $     */

/* Id: isakmp.c,v 1.74 2006/05/07 21:32:59 manubsd Exp */

/*
 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

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

#include <netinet/in.h>
#include <arpa/inet.h>

#include PATH_IPSEC_H

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include <netdb.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#ifdef ENABLE_HYBRID
#include <resolv.h>
#endif

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

#include "remoteconf.h"
#include "localconf.h"
#include "grabmyaddr.h"
#include "admin.h"
#include "privsep.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "oakley.h"
#include "evt.h"
#include "handler.h"
#include "ipsec_doi.h"
#include "pfkey.h"
#include "crypto_openssl.h"
#include "policy.h"
#include "algorithm.h"
#include "proposal.h"
#include "sainfo.h"
#include "isakmp_ident.h"
#include "isakmp_agg.h"
#include "isakmp_base.h"
#include "isakmp_quick.h"
#include "isakmp_inf.h"
#include "isakmp_newg.h"
#ifdef ENABLE_HYBRID
#include "vendorid.h"
#include "isakmp_xauth.h"
#include "isakmp_unity.h"
#include "isakmp_cfg.h"
#endif
#ifdef ENABLE_FRAG
#include "isakmp_frag.h"
#endif
#include "strnames.h"
#include "str2val.h"

#include <fcntl.h>

#ifdef ENABLE_NATT
# include "nattraversal.h"
#endif
# ifdef __linux__
#  include <linux/udp.h>
#  include <linux/ip.h>
#  ifndef SOL_UDP
#   define SOL_UDP 17
#  endif
# endif /* __linux__ */
# if defined(__NetBSD__) || defined(__FreeBSD__) ||     \
  (defined(__APPLE__) && defined(__MACH__))
#  include <netinet/in.h>
#  include <netinet/udp.h>
#  include <netinet/in_systm.h>
#  include <netinet/ip.h>
#  define SOL_UDP IPPROTO_UDP
# endif /* __NetBSD__ / __FreeBSD__ */

static int nostate1(struct ph1handle *, vchar_t *);
static int nostate2(struct ph2handle *, vchar_t *);

static int (*ph1exchange[][2][PHASE1ST_MAX])(struct ph1handle *, vchar_t *) = {
 /* error */
 { { 0 }, { 0 }, },
 /* Identity Protection exchange */
 {
  { nostate1, ident_i1send, nostate1, ident_i2recv, ident_i2send,
    ident_i3recv, ident_i3send, ident_i4recv, ident_i4send, nostate1, nostate1,},
  { nostate1, ident_r1recv, ident_r1send, ident_r2recv, ident_r2send,
    ident_r3recv, ident_r3send, nostate1, nostate1, nostate1, nostate1, },
 },
 /* Aggressive exchange */
 {
  { nostate1, agg_i1send, nostate1, agg_i2recv, agg_i2send,
    nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, },
  { nostate1, agg_r1recv, agg_r1send, agg_r2recv, agg_r2send,
    nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, },
 },
 /* Base exchange */
 {
  { nostate1, base_i1send, nostate1, base_i2recv, base_i2send,
    base_i3recv, base_i3send, nostate1, nostate1, nostate1, nostate1, },
  { nostate1, base_r1recv, base_r1send, base_r2recv, base_r2send,
    nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, },
 },
};

static int (*ph2exchange[][2][PHASE2ST_MAX])(struct ph2handle *, vchar_t *) = {
 /* error */
 { { 0 }, { 0 }, },
 /* Quick mode for IKE */
 {
  { nostate2, nostate2, quick_i1prep, nostate2, quick_i1send,
    quick_i2recv, quick_i2send, quick_i3recv, nostate2, nostate2, },
  { nostate2, quick_r1recv, quick_r1prep, nostate2, quick_r2send,
    quick_r3recv, quick_r3prep, quick_r3send, nostate2, nostate2, }
 },
};

static u_char r_ck0[] = { 0,0,0,0,0,0,0,0 }; /* used to verify the r_ck. */

static int isakmp_main(vchar_t *, struct sockaddr *, struct sockaddr *);
static int ph1_main(struct ph1handle *, vchar_t *);
static int quick_main(struct ph2handle *, vchar_t *);
static int isakmp_ph1begin_r(vchar_t *, struct sockaddr *, struct sockaddr *,
    uint8_t);
static int isakmp_ph2begin_i(struct ph1handle *, struct ph2handle *);
static int isakmp_ph2begin_r(struct ph1handle *, vchar_t *);
static int etypesw1(int);
static int etypesw2(int);
static int isakmp_ph1resend(struct ph1handle *);
static int isakmp_ph2resend(struct ph2handle *);

#ifdef ENABLE_FRAG
static int frag_handler(struct ph1handle *,
    vchar_t *, struct sockaddr *, struct sockaddr *);
#endif

/*
 * isakmp packet handler
 */
/*ARGSUSED*/
static int
isakmp_handler(void *ctx __unused, int so_isakmp)
{
        struct isakmp isakmp;
        union {
                char            buf[sizeof (isakmp) + 4];
                uint32_t        non_esp[2];
                struct          {
                                     struct udphdr udp;
#ifdef __linux
                                     struct iphdr ip;
#else
                                     struct ip ip;
#endif
                                     char buf[sizeof(isakmp) + 4];
                                } lbuf;
        } x;
        struct sockaddr_storage remote;
        struct sockaddr_storage local;
        unsigned int remote_len = sizeof(remote);
        unsigned int local_len = sizeof(local);
        ssize_t len = 0;
        int extralen = 0;
        vchar_t *buf = NULL, *tmpbuf = NULL;
        int error = -1, res;

        /* read message by MSG_PEEK */
        while ((len = recvfromto(so_isakmp, x.buf, sizeof(x),
                    MSG_PEEK, (struct sockaddr *)&remote, &remote_len,
                    (struct sockaddr *)&local, &local_len)) < 0) {
                if (errno == EINTR)
                        continue;
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to receive isakmp packet: %s\n",
                        strerror (errno));
                goto end;
        }

        /* keep-alive packet - ignore */
        if (len == 1 && (x.buf[0]&0xff) == 0xff) {
                /* Pull the keep-alive packet */
                if ((len = recvfrom(so_isakmp, (char *)x.buf, 1,
                    0, (struct sockaddr *)&remote, &remote_len)) != 1) {
                        plog(LLV_ERROR, LOCATION, NULL,
                            "failed to receive keep alive packet: %s\n",
                            strerror (errno));
                }
                goto end;
        }

        /* Lucent IKE in UDP encapsulation */
        {
#ifdef __linux__
                if (ntohs(x.lbuf.udp.dest) == 501) {
                        extralen += sizeof(x.lbuf.udp) + x.lbuf.ip.ihl;
                }
#else
                if (ntohs(x.lbuf.udp.uh_dport) == 501) {
                        extralen += sizeof(x.lbuf.udp) + x.lbuf.ip.ip_hl;
                }
#endif
        }

#ifdef ENABLE_NATT
        /* we don't know about portchange yet,
           look for non-esp marker instead */
        if (x.non_esp[0] == 0 && x.non_esp[1] != 0)
                extralen = NON_ESP_MARKER_LEN;
#endif

        /* now we know if there is an extra non-esp
           marker at the beginning or not */
        memcpy ((char *)&isakmp, x.buf + extralen, sizeof (isakmp));

        /* check isakmp header length, as well as sanity of header length */
        if (len < sizeof(isakmp) || ntohl(isakmp.len) < sizeof(isakmp)) {
                plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote,
                    "packet shorter than isakmp header size (%zu, %u, %zu)\n",
                    len, ntohl(isakmp.len), sizeof(isakmp));
                /* dummy receive */
                if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp),
                            0, (struct sockaddr *)&remote, &remote_len)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to receive isakmp packet: %s\n",
                                strerror (errno));
                }
                goto end;
        }

        /* reject it if the size is tooooo big. */
        if (ntohl(isakmp.len) > 0xffff) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "the length in the isakmp header is too big.\n");
                if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp),
                            0, (struct sockaddr *)&remote, &remote_len)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to receive isakmp packet: %s\n",
                                strerror (errno));
                }
                goto end;
        }

        /* read real message */
        if ((tmpbuf = vmalloc(ntohl(isakmp.len) + extralen)) == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to allocate reading buffer (%u Bytes)\n",
                        ntohl(isakmp.len) + extralen);
                /* dummy receive */
                if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp),
                            0, (struct sockaddr *)&remote, &remote_len)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to receive isakmp packet: %s\n",
                                strerror (errno));
                }
                goto end;
        }

        while ((len = recvfromto(so_isakmp, (char *)tmpbuf->v, tmpbuf->l,
                            0, (struct sockaddr *)&remote, &remote_len,
                            (struct sockaddr *)&local, &local_len)) < 0) {
                if (errno == EINTR)
                        continue;
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to receive isakmp packet: %s\n",
                        strerror (errno));
                goto end;
        }

        if ((buf = vmalloc(len - extralen)) == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to allocate reading buffer (%zu Bytes)\n",
                        (len - extralen));
                goto end;
        }

        memcpy (buf->v, tmpbuf->v + extralen, buf->l);

        len -= extralen;

        if (len != buf->l) {
                plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote,
                        "received invalid length (%zd != %zu), why ?\n",
                        len, buf->l);
                goto end;
        }

        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
        plog(LLV_DEBUG, LOCATION, NULL,
                "%zd bytes message received %s\n",
                len, saddr2str_fromto("from %s to %s",
                        (struct sockaddr *)&remote,
                        (struct sockaddr *)&local));
        plogdump(LLV_DEBUG, buf->v, buf->l);

        /* avoid packets with malicious port/address */
        if (extract_port((struct sockaddr *)&remote) == 0) {
                plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote,
                        "src port == 0 (valid as UDP but not with IKE)\n");
                goto end;
        }

        /* XXX: check sender whether to be allowed or not to accept */

        /* XXX: I don't know how to check isakmp half connection attack. */

        /* simply reply if the packet was processed. */
        res=check_recvdpkt((struct sockaddr *)&remote,(struct sockaddr *)&local, buf);
        if (res) {
                plog(LLV_NOTIFY, LOCATION, NULL,
                        "the packet is retransmitted by %s (%d).\n",
                         saddr2str((struct sockaddr *)&remote), res);
                error = 0;
                goto end;
        }

        /* isakmp main routine */
        if (isakmp_main(buf, (struct sockaddr *)&remote,
                        (struct sockaddr *)&local) != 0) goto end;

        error = 0;

end:
        if (tmpbuf != NULL)
                vfree(tmpbuf);
        if (buf != NULL)
                vfree(buf);
        return error;
}

/*
 * main processing to handle isakmp payload
 */
static int
isakmp_main(vchar_t *msg, struct sockaddr *remote, struct sockaddr *local)
{
        struct isakmp *isakmp = (struct isakmp *)msg->v;
        isakmp_index *index1 = (isakmp_index *)isakmp;
        uint32_t msgid = isakmp->msgid;
        struct ph1handle *iph1;

#ifdef HAVE_PRINT_ISAKMP_C
        isakmp_printpacket(msg, remote, local, 0);
#endif

        /* the initiator's cookie must not be zero */
        if (memcmp(&isakmp->i_ck, r_ck0, sizeof(cookie_t)) == 0) {
                plog(LLV_ERROR, LOCATION, remote,
                        "malformed cookie received.\n");
                return -1;
        }

        /* Check the Major and Minor Version fields. */
        /*
         * XXX Is is right to check version here ?
         * I think it may no be here because the version depends
         * on exchange status.
         */
        if (isakmp->v < ISAKMP_VERSION_NUMBER) {
                if (ISAKMP_GETMAJORV(isakmp->v) < ISAKMP_MAJOR_VERSION) {
                        plog(LLV_ERROR, LOCATION, remote,
                                "invalid major version %d.\n",
                                ISAKMP_GETMAJORV(isakmp->v));
                        return -1;
                }
#if ISAKMP_MINOR_VERSION > 0
                if (ISAKMP_GETMINORV(isakmp->v) < ISAKMP_MINOR_VERSION) {
                        plog(LLV_ERROR, LOCATION, remote,
                                "invalid minor version %d.\n",
                                ISAKMP_GETMINORV(isakmp->v));
                        return -1;
                }
#endif
        }

        /* check the Flags field. */
        /* XXX How is the exclusive check, E and A ? */
        if (isakmp->flags & ~(ISAKMP_FLAG_E | ISAKMP_FLAG_C | ISAKMP_FLAG_A)) {
                plog(LLV_ERROR, LOCATION, remote,
                        "invalid flag 0x%02x.\n", isakmp->flags);
                return -1;
        }

        /* ignore commit bit. */
        if (ISSET(isakmp->flags, ISAKMP_FLAG_C)) {
                if (isakmp->msgid == 0) {
                        isakmp_info_send_nx(isakmp, remote, local,
                                ISAKMP_NTYPE_INVALID_FLAGS, NULL);
                        plog(LLV_ERROR, LOCATION, remote,
                                "Commit bit on phase1 forbidden.\n");
                        return -1;
                }
        }

        iph1 = getph1byindex(index1);
        if (iph1 != NULL) {
                /* validity check */
                if (memcmp(&isakmp->r_ck, r_ck0, sizeof(cookie_t)) == 0 &&
                    iph1->side == INITIATOR) {
                        plog(LLV_DEBUG, LOCATION, remote,
                                "malformed cookie received or "
                                "the initiator's cookies collide.\n");
                        return -1;
                }

#ifdef ENABLE_NATT
                /* Floating ports for NAT-T */
                if (NATT_AVAILABLE(iph1) &&
                    ! (iph1->natt_flags & NAT_PORTS_CHANGED) &&
                    ((cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) ||
                     (cmpsaddr(iph1->local, local) != CMPSADDR_MATCH)))
                {
                        /* prevent memory leak */
                        racoon_free(iph1->remote);
                        racoon_free(iph1->local);
                        iph1->remote = NULL;
                        iph1->local = NULL;

                        /* copy-in new addresses */
                        iph1->remote = dupsaddr(remote);
                        if (iph1->remote == NULL) {
                                plog(LLV_ERROR, LOCATION, iph1->remote,
                                   "phase1 failed: dupsaddr failed.\n");
                                remph1(iph1);
                                delph1(iph1);
                                return -1;
                        }
                        iph1->local = dupsaddr(local);
                        if (iph1->local == NULL) {
                                plog(LLV_ERROR, LOCATION, iph1->remote,
                                   "phase1 failed: dupsaddr failed.\n");
                                remph1(iph1);
                                delph1(iph1);
                                return -1;
                        }

                        /* set the flag to prevent further port floating
                           (FIXME: should we allow it? E.g. when the NAT gw
                            is rebooted?) */
                        iph1->natt_flags |= NAT_PORTS_CHANGED | NAT_ADD_NON_ESP_MARKER;

                        /* print some neat info */
                        plog (LLV_INFO, LOCATION, NULL,
                              "NAT-T: ports changed to: %s\n",
                              saddr2str_fromto ("%s<->%s", iph1->remote, iph1->local));

                        natt_keepalive_add_ph1 (iph1);
                }
#endif

                /* must be same addresses in one stream of a phase at least. */
                if (cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) {
                        char *saddr_db, *saddr_act;

                        saddr_db = racoon_strdup(saddr2str(iph1->remote));
                        saddr_act = racoon_strdup(saddr2str(remote));
                        STRDUP_FATAL(saddr_db);
                        STRDUP_FATAL(saddr_act);

                        plog(LLV_WARNING, LOCATION, remote,
                                "remote address mismatched. db=%s, act=%s\n",
                                saddr_db, saddr_act);

                        racoon_free(saddr_db);
                        racoon_free(saddr_act);
                }

                /*
                 * don't check of exchange type here because other type will be
                 * with same index, for example, informational exchange.
                 */

                /* XXX more acceptable check */
        }

        switch (isakmp->etype) {
        case ISAKMP_ETYPE_IDENT:
        case ISAKMP_ETYPE_AGG:
        case ISAKMP_ETYPE_BASE:
                /* phase 1 validity check */
                if (isakmp->msgid != 0) {
                        plog(LLV_ERROR, LOCATION, remote,
                                "message id should be zero in phase1.\n");
                        return -1;
                }

                /* search for isakmp status record of phase 1 */
                if (iph1 == NULL) {
                        /*
                         * the packet must be the 1st message from a initiator
                         * or the 2nd message from the responder.
                         */

                        /* search for phase1 handle by index without r_ck */
                        iph1 = getph1byindex0(index1);
                        if (iph1 == NULL) {
                                /*it must be the 1st message from a initiator.*/
                                if (memcmp(&isakmp->r_ck, r_ck0,
                                        sizeof(cookie_t)) != 0) {

                                        plog(LLV_DEBUG, LOCATION, remote,
                                                "malformed cookie received "
                                                "or the spi expired.\n");
                                        return -1;
                                }

                                /* it must be responder's 1st exchange. */
                                if (isakmp_ph1begin_r(msg, remote, local,
                                        isakmp->etype) < 0)
                                        return -1;
                                break;

                                /*NOTREACHED*/
                        }

                        /* it must be the 2nd message from the responder. */
                        if (iph1->side != INITIATOR) {
                                plog(LLV_DEBUG, LOCATION, remote,
                                        "malformed cookie received. "
                                        "it has to be as the initiator.  %s\n",
                                        isakmp_pindex(&iph1->index, 0));
                                return -1;
                        }
                }

                /*
                 * Don't delete phase 1 handler when the exchange type
                 * in handler is not equal to packet's one because of no
                 * authencication completed.
                 */
                if (iph1->etype != isakmp->etype) {
                        plog(LLV_ERROR, LOCATION, iph1->remote,
                                "exchange type is mismatched: "
                                "db=%s packet=%s, ignore it.\n",
                                s_isakmp_etype(iph1->etype),
                                s_isakmp_etype(isakmp->etype));
                        return -1;
                }

#ifdef ENABLE_FRAG
                if (isakmp->np == ISAKMP_NPTYPE_FRAG)
                        return frag_handler(iph1, msg, remote, local);
#endif

                /* call main process of phase 1 */
                if (ph1_main(iph1, msg) < 0) {
                        plog(LLV_ERROR, LOCATION, iph1->remote,
                                "phase1 negotiation failed.\n");
                        remph1(iph1);
                        delph1(iph1);
                        return -1;
                }
                break;

        case ISAKMP_ETYPE_AUTH:
                plog(LLV_INFO, LOCATION, remote,
                        "unsupported exchange %d received.\n",
                        isakmp->etype);
                break;

        case ISAKMP_ETYPE_INFO:
        case ISAKMP_ETYPE_ACKINFO:
                /*
                 * iph1 must be present for Information message.
                 * if iph1 is null then trying to get the phase1 status
                 * as the packet from responder again initiator's 1st
                 * exchange in phase 1.
                 * NOTE: We think such informational exchange should be ignored.
                 */
                if (iph1 == NULL) {
                        iph1 = getph1byindex0(index1);
                        if (iph1 == NULL) {
                                plog(LLV_ERROR, LOCATION, remote,
                                        "unknown Informational "
                                        "exchange received.\n");
                                return -1;
                        }
                        if (cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) {
                                plog(LLV_WARNING, LOCATION, remote,
                                        "remote address mismatched. "
                                        "db=%s\n",
                                        saddr2str(iph1->remote));
                        }
                }

#ifdef ENABLE_FRAG
                if (isakmp->np == ISAKMP_NPTYPE_FRAG)
                        return frag_handler(iph1, msg, remote, local);
#endif

                if (isakmp_info_recv(iph1, msg) < 0)
                        return -1;
                break;

        case ISAKMP_ETYPE_QUICK:
        {
                struct ph2handle *iph2;

                if (iph1 == NULL) {
                        isakmp_info_send_nx(isakmp, remote, local,
                                ISAKMP_NTYPE_INVALID_COOKIE, NULL);
                        plog(LLV_ERROR, LOCATION, remote,
                                "can't start the quick mode, "
                                "there is no ISAKMP-SA, %s\n",
                                isakmp_pindex((isakmp_index *)&isakmp->i_ck,
                                        isakmp->msgid));
                        return -1;
                }
#ifdef ENABLE_HYBRID
                /* Reinit the IVM if it's still there */
                if (iph1->mode_cfg && iph1->mode_cfg->ivm) {
                        oakley_delivm(iph1->mode_cfg->ivm);
                        iph1->mode_cfg->ivm = NULL;
                }
#endif
#ifdef ENABLE_FRAG
                if (isakmp->np == ISAKMP_NPTYPE_FRAG)
                        return frag_handler(iph1, msg, remote, local);
#endif

                /* check status of phase 1 whether negotiated or not. */
                if (iph1->status != PHASE1ST_ESTABLISHED &&
                    iph1->status != PHASE1ST_DYING) {
                        plog(LLV_ERROR, LOCATION, remote,
                                "can't start the quick mode, "
                                "there is no valid ISAKMP-SA, %s\n",
                                isakmp_pindex(&iph1->index, iph1->msgid));
                        return -1;
                }

                /* search isakmp phase 2 status record. */
                iph2 = getph2bymsgid(iph1, msgid);
                if (iph2 == NULL) {
                        /* it must be new negotiation as responder */
                        if (isakmp_ph2begin_r(iph1, msg) < 0)
                                return -1;
                        return 0;
                        /*NOTREACHED*/
                }

                /* commit bit. */
                /* XXX
                 * we keep to set commit bit during negotiation.
                 * When SA is configured, bit will be reset.
                 * XXX
                 * don't initiate commit bit.  should be fixed in the future.
                 */
                if (ISSET(isakmp->flags, ISAKMP_FLAG_C))
                        iph2->flags |= ISAKMP_FLAG_C;

                /* call main process of quick mode */
                if (quick_main(iph2, msg) < 0) {
                        plog(LLV_ERROR, LOCATION, iph1->remote,
                                "phase2 negotiation failed.\n");
                        remph2(iph2);
                        delph2(iph2);
                        return -1;
                }
        }
                break;

        case ISAKMP_ETYPE_NEWGRP:
                if (iph1 == NULL) {
                        plog(LLV_ERROR, LOCATION, remote,
                                "Unknown new group mode exchange, "
                                "there is no ISAKMP-SA.\n");
                        return -1;
                }

#ifdef ENABLE_FRAG
                if (isakmp->np == ISAKMP_NPTYPE_FRAG)
                        return frag_handler(iph1, msg, remote, local);
#endif

                isakmp_newgroup_r(iph1, msg);
                break;

#ifdef ENABLE_HYBRID
        case ISAKMP_ETYPE_CFG:
                if (iph1 == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                             "mode config %d from %s, "
                             "but we have no ISAKMP-SA.\n",
                             isakmp->etype, saddr2str(remote));
                        return -1;
                }

#ifdef ENABLE_FRAG
                if (isakmp->np == ISAKMP_NPTYPE_FRAG)
                        return frag_handler(iph1, msg, remote, local);
#endif

                isakmp_cfg_r(iph1, msg);
                break;
#endif

        case ISAKMP_ETYPE_NONE:
        default:
                plog(LLV_ERROR, LOCATION, NULL,
                        "Invalid exchange type %d from %s.\n",
                        isakmp->etype, saddr2str(remote));
                return -1;
        }

        return 0;
}

/*
 * main function of phase 1.
 */
static int
ph1_main(struct ph1handle *iph1, vchar_t *msg)
{
        int error;
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif

        /* ignore a packet */
        if (iph1->status >= PHASE1ST_ESTABLISHED)
                return 0;

#ifdef ENABLE_STATS
        gettimeofday(&start, NULL);
#endif
        /* receive */
        if (ph1exchange[etypesw1(iph1->etype)]
                       [iph1->side]
                       [iph1->status] == NULL) {
                plog(LLV_ERROR, LOCATION, iph1->remote,
                        "why isn't the function defined.\n");
                return -1;
        }
        error = (ph1exchange[etypesw1(iph1->etype)]
                            [iph1->side]
                            [iph1->status])(iph1, msg);
        if (error != 0) {

                /* XXX
                 * When an invalid packet is received on phase1, it should
                 * be selected to process this packet.  That is to respond
                 * with a notify and delete phase 1 handler, OR not to respond
                 * and keep phase 1 handler. However, in PHASE1ST_START when
                 * acting as RESPONDER we must not keep phase 1 handler or else
                 * it will stay forever.
                 */

                if (iph1->side == RESPONDER && iph1->status == PHASE1ST_START) {
                        plog(LLV_ERROR, LOCATION, iph1->remote,
                                "failed to pre-process ph1 packet (side: %d, status %d).\n",
                                iph1->side, iph1->status);
                        return -1;
                } else {
                        /* ignore the error and keep phase 1 handler */
                        return 0;
                }
        }

#ifndef ENABLE_FRAG
        /* free resend buffer */
        if (iph1->sendbuf == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "no buffer found as sendbuf\n");
                return -1;
        }
#endif

        VPTRINIT(iph1->sendbuf);

        /* turn off schedule */
        sched_cancel(&iph1->scr);

        /* send */
        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
        if ((ph1exchange[etypesw1(iph1->etype)]
                        [iph1->side]
                        [iph1->status])(iph1, msg) != 0) {
                plog(LLV_ERROR, LOCATION, iph1->remote,
                        "failed to process ph1 packet (side: %d, status: %d).\n",
                        iph1->side, iph1->status);
                return -1;
        }

#ifdef ENABLE_STATS
        gettimeofday(&end, NULL);
        syslog(LOG_NOTICE, "%s(%s): %8.6f",
                "phase1", s_isakmp_state(iph1->etype, iph1->side, iph1->status),
                timedelta(&start, &end));
#endif
        if (iph1->status == PHASE1ST_ESTABLISHED) {

#ifdef ENABLE_STATS
                gettimeofday(&iph1->end, NULL);
                syslog(LOG_NOTICE, "%s(%s): %8.6f",
                        "phase1", s_isakmp_etype(iph1->etype),
                        timedelta(&iph1->start, &iph1->end));
#endif

                /* save created date. */
                (void)time(&iph1->created);

                /* migrate ph2s from dying ph1s */
                migrate_dying_ph12(iph1);

                /* add to the schedule to expire, and seve back pointer. */
                if (ph1_rekey_enabled(iph1)) {
                        sched_schedule(&iph1->sce,
                                       iph1->approval->lifetime *
                                       PFKEY_SOFT_LIFETIME_RATE / 100,
                                       isakmp_ph1dying_stub);
                } else {
                        sched_schedule(&iph1->sce, iph1->approval->lifetime,
                                       isakmp_ph1expire_stub);
                }

#ifdef ENABLE_HYBRID
                if (iph1->mode_cfg->flags & ISAKMP_CFG_VENDORID_XAUTH) {
                        switch (iph1->approval->authmethod) {
                        case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R:
                        case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R:
                                xauth_sendreq(iph1);
                                /* XXX Don't process INITIAL_CONTACT */
                                iph1->rmconf->ini_contact = 0;
                                break;
                        case OAKLEY_ATTR_AUTH_METHOD_RSASIG:
                                if (iph1->rmconf->mode_cfg)
                                        error = isakmp_cfg_getconfig(iph1);
                                break;
                        default:
                                break;
                        }
                }
#endif
#ifdef ENABLE_DPD
                /* Schedule the r_u_there.... */
                if(iph1->dpd_support && iph1->rmconf->dpd_interval)
                        isakmp_sched_r_u(iph1, 0);
#endif

                /* INITIAL-CONTACT processing */
                /* don't anything if local test mode. */
                if (!f_local
                 && iph1->rmconf->ini_contact && !getcontacted(iph1->remote)) {
                        /* send INITIAL-CONTACT */
                        isakmp_info_send_n1(iph1,
                                        ISAKMP_NTYPE_INITIAL_CONTACT, NULL);
                        /* insert a node into contacted list. */
                        if (inscontacted(iph1->remote) == -1) {
                                plog(LLV_ERROR, LOCATION, iph1->remote,
                                        "failed to add contacted list.\n");
                                /* ignore */
                        }
                }
                if (iph1->initial_contact_received)
                        isakmp_info_recv_initialcontact(iph1, NULL);

                log_ph1established(iph1);
                plog(LLV_DEBUG, LOCATION, NULL, "===\n");

                /*
                 * SA up shell script hook: do it now,except if
                 * ISAKMP mode config was requested. In the later
                 * case it is done when we receive the configuration.
                 */
                if ((iph1->status == PHASE1ST_ESTABLISHED) &&
                    !iph1->rmconf->mode_cfg) {
                        switch (iph1->approval->authmethod) {
#ifdef ENABLE_HYBRID
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R:
                        case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R:
                        /* Unimplemeted... */
                        case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R:
                        case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R:
                                break;
#endif
                        default:
                                script_hook(iph1, SCRIPT_PHASE1_UP);
                                break;
                        }
                }
                if ((iph1->rmconf->mode_cfg) &&
                    !(iph1->mode_cfg->flags & ISAKMP_CFG_VENDORID_XAUTH)) {
                        error = isakmp_cfg_getconfig(iph1);
                }
        }

        return 0;
}

/*
 * main function of quick mode.
 */
static int
quick_main(struct ph2handle *iph2, vchar_t *msg)
{
        struct isakmp *isakmp = (struct isakmp *)msg->v;
        int error;
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif

        /* ignore a packet */
        if (iph2->status == PHASE2ST_ESTABLISHED
         || iph2->status == PHASE2ST_GETSPISENT)
                return 0;

#ifdef ENABLE_STATS
        gettimeofday(&start, NULL);
#endif

        /* receive */
        if (ph2exchange[etypesw2(isakmp->etype)]
                       [iph2->side]
                       [iph2->status] == NULL) {
                plog(LLV_ERROR, LOCATION, iph2->ph1->remote,
                        "why isn't the function defined.\n");
                return -1;
        }
        error = (ph2exchange[etypesw2(isakmp->etype)]
                            [iph2->side]
                            [iph2->status])(iph2, msg);
        if (error != 0) {
                plog(LLV_ERROR, LOCATION, iph2->ph1->remote,
                        "failed to pre-process ph2 packet (side: %d, status %d).\n",
                        iph2->side, iph2->status);
                if (error == ISAKMP_INTERNAL_ERROR)
                        return 0;
                isakmp_info_send_n1(iph2->ph1, error, NULL);
                return -1;
        }

        /* when using commit bit, status will be reached here. */
        if (iph2->status == PHASE2ST_ADDSA)
                return 0;

        /* free resend buffer */
        if (iph2->sendbuf == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "no buffer found as sendbuf\n");
                return -1;
        }
        VPTRINIT(iph2->sendbuf);

        /* turn off schedule */
        sched_cancel(&iph2->scr);

        /* send */
        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
        if ((ph2exchange[etypesw2(isakmp->etype)]
                        [iph2->side]
                        [iph2->status])(iph2, msg) != 0) {
                plog(LLV_ERROR, LOCATION, iph2->ph1->remote,
                        "failed to process ph2 packet (side: %d, status: %d).\n",
                        iph2->side, iph2->status);
                return -1;
        }

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

        return 0;
}

/* new negotiation of phase 1 for initiator */
struct ph1handle *
isakmp_ph1begin_i(struct remoteconf *rmconf,
    struct sockaddr *remote, struct sockaddr *local)
{
        struct ph1handle *iph1;
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif

        /* get new entry to isakmp status table. */
        iph1 = newph1();
        if (iph1 == NULL)
                return NULL;

        iph1->status = PHASE1ST_START;
        iph1->rmconf = rmconf;
        iph1->side = INITIATOR;
        iph1->version = ISAKMP_VERSION_NUMBER;
        iph1->msgid = 0;
        iph1->flags = 0;
        iph1->ph2cnt = 0;
#ifdef HAVE_GSSAPI
        iph1->gssapi_state = NULL;
#endif
#ifdef ENABLE_HYBRID
        if ((iph1->mode_cfg = isakmp_cfg_mkstate()) == NULL) {
                delph1(iph1);
                return NULL;
        }
#endif
#ifdef ENABLE_FRAG

        if(rmconf->ike_frag == ISAKMP_FRAG_FORCE)
                iph1->frag = 1;
        else
                iph1->frag = 0;
        iph1->frag_last_index = 0;
        iph1->frag_chain = NULL;
#endif
        iph1->approval = NULL;

        /* XXX copy remote address */
        if (copy_ph1addresses(iph1, rmconf, remote, local) < 0) {
                delph1(iph1);
                return NULL;
        }

        (void)insph1(iph1);

        /* start phase 1 exchange */
        iph1->etype = rmconf->etypes->type;

        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
    {
        char *a;

        a = racoon_strdup(saddr2str(iph1->local));
        STRDUP_FATAL(a);

        plog(LLV_INFO, LOCATION, NULL,
                "initiate new phase 1 negotiation: %s<=>%s\n",
                a, saddr2str(iph1->remote));
        racoon_free(a);
    }
        plog(LLV_INFO, LOCATION, NULL,
                "begin %s mode.\n",
                s_isakmp_etype(iph1->etype));

#ifdef ENABLE_STATS
        gettimeofday(&iph1->start, NULL);
        gettimeofday(&start, NULL);
#endif
        /* start exchange */
        if ((ph1exchange[etypesw1(iph1->etype)]
                        [iph1->side]
                        [iph1->status])(iph1, NULL) != 0) {
                /* failed to start phase 1 negotiation */
                remph1(iph1);
                delph1(iph1);

                return NULL;
        }

#ifdef ENABLE_STATS
        gettimeofday(&end, NULL);
        syslog(LOG_NOTICE, "%s(%s): %8.6f",
                "phase1",
                s_isakmp_state(iph1->etype, iph1->side, iph1->status),
                timedelta(&start, &end));
#endif

        return iph1;
}

/* new negotiation of phase 1 for responder */
static int
isakmp_ph1begin_r(vchar_t *msg, struct sockaddr *remote, struct sockaddr *local,
    uint8_t etype)
{
        struct isakmp *isakmp = (struct isakmp *)msg->v;
        struct ph1handle *iph1;
        struct rmconfselector rmsel;
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif

        /* check if this etype is allowed */
        memset(&rmsel, 0, sizeof(rmsel));
        rmsel.remote = remote;
        if (enumrmconf(&rmsel, check_etypeok, (void *) (intptr_t) etype) == 0) {
                plog(LLV_ERROR, LOCATION, remote,
                     "exchange %s not allowed in any applicable rmconf.\n",
                     s_isakmp_etype(etype));
                return -1;
        }

        /* get new entry to isakmp status table. */
        iph1 = newph1();
        if (iph1 == NULL)
                return -1;

        memcpy(&iph1->index.i_ck, &isakmp->i_ck, sizeof(iph1->index.i_ck));
        iph1->status = PHASE1ST_START;
        iph1->flags = 0;
        iph1->side = RESPONDER;
        iph1->etype = etype;
        iph1->version = isakmp->v;
        iph1->msgid = 0;
#ifdef HAVE_GSSAPI
        iph1->gssapi_state = NULL;
#endif
#ifdef ENABLE_HYBRID
        if ((iph1->mode_cfg = isakmp_cfg_mkstate()) == NULL) {
                delph1(iph1);
                return -1;
        }
#endif
#ifdef ENABLE_FRAG
        iph1->frag = 0;
        iph1->frag_last_index = 0;
        iph1->frag_chain = NULL;
#endif
        iph1->approval = NULL;

#ifdef ENABLE_NATT
        /* RFC3947 says that we MUST accept new phases1 on NAT-T floated port.
         * We have to setup this flag now to correctly generate the first reply.
         * Don't know if a better check could be done for that ?
         */
        if(extract_port(local) == lcconf->port_isakmp_natt)
                iph1->natt_flags |= (NAT_PORTS_CHANGED);
#endif

        /* copy remote address; remote and local always contain
         * port numbers so rmconf is not needed */
        if (copy_ph1addresses(iph1, NULL, remote, local) < 0) {
                delph1(iph1);
                return -1;
        }
        (void)insph1(iph1);

        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
    {
        char *a;

        a = racoon_strdup(saddr2str(iph1->local));
        STRDUP_FATAL(a);

        plog(LLV_INFO, LOCATION, NULL,
                "respond new phase 1 negotiation: %s<=>%s\n",
                a, saddr2str(iph1->remote));
        racoon_free(a);
    }
        plog(LLV_INFO, LOCATION, NULL,
                "begin %s mode.\n", s_isakmp_etype(etype));

#ifdef ENABLE_STATS
        gettimeofday(&iph1->start, NULL);
        gettimeofday(&start, NULL);
#endif

#ifndef ENABLE_FRAG

        /* start exchange */
        if ((ph1exchange[etypesw1(iph1->etype)]
                        [iph1->side]
                        [iph1->status])(iph1, msg) < 0
         || (ph1exchange[etypesw1(iph1->etype)]
                        [iph1->side]
                        [iph1->status])(iph1, msg) < 0) {
                plog(LLV_ERROR, LOCATION, remote,
                        "failed to process ph1 packet (side: %d, status: %d).\n",
                        iph1->side, iph1->status);
                remph1(iph1);
                delph1(iph1);
                return -1;
        }

#ifdef ENABLE_STATS
        gettimeofday(&end, NULL);
        syslog(LOG_NOTICE, "%s(%s): %8.6f",
                "phase1",
                s_isakmp_state(iph1->etype, iph1->side, iph1->status),
                timedelta(&start, &end));
#endif

        return 0;

#else /* ENABLE_FRAG */

        /* now that we have a phase1 handle, feed back into our
         * main receive function to catch fragmented packets
         */

        return isakmp_main(msg, remote, local);

#endif /* ENABLE_FRAG */

}

/* new negotiation of phase 2 for initiator */
static int
isakmp_ph2begin_i(struct ph1handle *iph1, struct ph2handle *iph2)
{
#ifdef ENABLE_HYBRID
        if (xauth_check(iph1) != 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                    "Attempt to start phase 2 whereas Xauth failed\n");
                return -1;
        }
#endif

        /* fixup ph2 ports for this ph1 */
        if (extract_port(iph2->src) == 0)
                set_port(iph2->src, extract_port(iph1->local));
        if (extract_port(iph2->dst) == 0)
                set_port(iph2->dst, extract_port(iph1->remote));

        /* found ISAKMP-SA. */
        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
        plog(LLV_DEBUG, LOCATION, NULL, "begin QUICK mode.\n");
    {
        char *a;
        a = racoon_strdup(saddr2str(iph2->src));
        STRDUP_FATAL(a);

        plog(LLV_INFO, LOCATION, NULL,
                "initiate new phase 2 negotiation: %s<=>%s\n",
                a, saddr2str(iph2->dst));
        racoon_free(a);
    }

#ifdef ENABLE_STATS
        gettimeofday(&iph2->start, NULL);
#endif
        if (iph2->status != PHASE2ST_EXPIRED) /* Phase 1 is already bound (ongoing rekeying) */
                bindph12(iph1, iph2);
        iph2->status = PHASE2ST_STATUS2;

        if ((ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)]
                         [iph2->side]
                         [iph2->status])(iph2, NULL) < 0) {
                /* release ipsecsa handler due to internal error. */
                remph2(iph2);
                return -1;
        }
        return 0;
}

/* new negotiation of phase 2 for responder */
static int
isakmp_ph2begin_r(struct ph1handle *iph1, vchar_t *msg)
{
        struct isakmp *isakmp = (struct isakmp *)msg->v;
        struct ph2handle *iph2 = 0;
        int error;
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif
#ifdef ENABLE_HYBRID
        if (xauth_check(iph1) != 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                    "Attempt to start phase 2 whereas Xauth failed\n");
                return -1;
        }
#endif

        iph2 = newph2();
        if (iph2 == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to allocate phase2 entry.\n");
                return -1;
        }

        iph2->side = RESPONDER;
        iph2->status = PHASE2ST_START;
        iph2->flags = isakmp->flags;
        iph2->msgid = isakmp->msgid;
        iph2->seq = pk_getseq();
        iph2->ivm = oakley_newiv2(iph1, iph2->msgid);
        if (iph2->ivm == NULL) {
                delph2(iph2);
                return -1;
        }
        iph2->dst = dupsaddr(iph1->remote);     /* XXX should be considered */
        if (iph2->dst == NULL) {
                delph2(iph2);
                return -1;
        }
        iph2->src = dupsaddr(iph1->local);      /* XXX should be considered */
        if (iph2->src == NULL) {
                delph2(iph2);
                return -1;
        }

        /* add new entry to isakmp status table */
        insph2(iph2);
        bindph12(iph1, iph2);

        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
    {
        char *a;

        a = racoon_strdup(saddr2str(iph2->src));
        STRDUP_FATAL(a);

        plog(LLV_INFO, LOCATION, NULL,
                "respond new phase 2 negotiation: %s<=>%s\n",
                a, saddr2str(iph2->dst));
        racoon_free(a);
    }

#ifdef ENABLE_STATS
        gettimeofday(&start, NULL);
#endif

        error = (ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)]
                           [iph2->side]
                           [iph2->status])(iph2, msg);
        if (error != 0) {
                plog(LLV_ERROR, LOCATION, iph1->remote,
                        "failed to pre-process ph2 packet (side: %d, status: %d).\n",
                        iph2->side, iph2->status);
                if (error != ISAKMP_INTERNAL_ERROR)
                        isakmp_info_send_n1(iph2->ph1, error, NULL);
                /*
                 * release handler because it's wrong that ph2handle is kept
                 * after failed to check message for responder's.
                 */
                remph2(iph2);
                delph2(iph2);
                return -1;
        }

        /* send */
        plog(LLV_DEBUG, LOCATION, NULL, "===\n");
        if ((ph2exchange[etypesw2(isakmp->etype)]
                        [iph2->side]
                        [iph2->status])(iph2, msg) < 0) {
                plog(LLV_ERROR, LOCATION, iph2->ph1->remote,
                        "failed to process ph2 packet (side: %d, status: %d).\n",
                        iph2->side, iph2->status);
                /* don't release handler */
                return -1;
        }
#ifdef ENABLE_STATS
        gettimeofday(&end, NULL);
        syslog(LOG_NOTICE, "%s(%s): %8.6f",
                "phase2",
                s_isakmp_state(ISAKMP_ETYPE_QUICK, iph2->side, iph2->status),
                timedelta(&start, &end));
#endif

        return 0;
}

/*
 * parse ISAKMP payloads, without ISAKMP base header.
 */
vchar_t *
isakmp_parsewoh(int np0, struct isakmp_gen *gen, int len)
{
        u_char np = np0 & 0xff;
        int tlen, plen;
        vchar_t *result;
        struct isakmp_parse_t *p, *ep;

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

        /*
         * 5 is a magic number, but any value larger than 2 should be fine
         * as we do vrealloc() in the following loop.
         */
        result = vmalloc(sizeof(struct isakmp_parse_t) * 5);
        if (result == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get buffer.\n");
                return NULL;
        }
        p = (struct isakmp_parse_t *)result->v;
        ep = (struct isakmp_parse_t *)(result->v + result->l - sizeof(*ep));

        tlen = len;

        /* parse through general headers */
        while (0 < tlen && np != ISAKMP_NPTYPE_NONE) {
                if (tlen <= sizeof(struct isakmp_gen)) {
                        /* don't send information, see isakmp_ident_r1() */
                        plog(LLV_ERROR, LOCATION, NULL,
                                "invalid length of payload\n");
                        vfree(result);
                        return NULL;
                }

                plog(LLV_DEBUG, LOCATION, NULL,
                        "seen nptype=%u(%s)\n", np, s_isakmp_nptype(np));

                p->type = np;
                p->len = ntohs(gen->len);
                if (p->len < sizeof(struct isakmp_gen) || p->len > tlen) {
                        plog(LLV_DEBUG, LOCATION, NULL,
                                "invalid length of payload\n");
                        vfree(result);
                        return NULL;
                }
                p->ptr = gen;
                p++;
                if (ep <= p) {
                        off_t off;

                        off = p - (struct isakmp_parse_t *)result->v;
                        result = vrealloc(result, result->l * 2);
                        if (result == NULL) {
                                plog(LLV_DEBUG, LOCATION, NULL,
                                        "failed to realloc buffer.\n");
                                vfree(result);
                                return NULL;
                        }
                        ep = (struct isakmp_parse_t *)
                                (result->v + result->l - sizeof(*ep));
                        p = (struct isakmp_parse_t *)result->v;
                        p += off;
                }

                np = gen->np;
                plen = ntohs(gen->len);
                gen = (struct isakmp_gen *)((caddr_t)gen + plen);
                tlen -= plen;
        }
        p->type = ISAKMP_NPTYPE_NONE;
        p->len = 0;
        p->ptr = NULL;

        plog(LLV_DEBUG, LOCATION, NULL, "succeed.\n");

        return result;
}

/*
 * parse ISAKMP payloads, including ISAKMP base header.
 */
vchar_t *
isakmp_parse(vchar_t *buf)
{
        struct isakmp *isakmp = (struct isakmp *)buf->v;
        struct isakmp_gen *gen;
        size_t tlen;
        vchar_t *result;
        u_char np;

        np = isakmp->np;
        gen = (struct isakmp_gen *)(buf->v + sizeof(*isakmp));
        tlen = buf->l - sizeof(struct isakmp);
        result = isakmp_parsewoh(np, gen, tlen);

        return result;
}

/* %%% */
int
isakmp_init()
{
        /* initialize a isakmp status table */
        initph1tree();
        initph2tree();
        initctdtree();
        init_recvdpkt();

        return 0;
}

/*
 * make strings containing i_cookie + r_cookie + msgid
 */
const char *
isakmp_pindex(const isakmp_index *index1, const uint32_t msgid)
{
        static char buf[64];
        const u_char *p;
        int i, j;

        memset(buf, 0, sizeof(buf));

        /* copy index */
        p = (const u_char *)index1;
        for (j = 0, i = 0; i < sizeof(isakmp_index); i++) {
                snprintf((char *)&buf[j], sizeof(buf) - j, "%02x", p[i]);
                j += 2;
                switch (i) {
                case 7:
                        buf[j++] = ':';
                }
        }

        if (msgid == 0)
                return buf;

        /* copy msgid */
        snprintf((char *)&buf[j], sizeof(buf) - j, ":%08x", ntohs(msgid));

        return buf;
}

/* open ISAKMP sockets. */
int
isakmp_open(struct sockaddr *addr, int udp_encap)
{
        const int yes = 1;
        int fd;
        struct sockaddr_in *sin = (struct sockaddr_in *) addr;
#ifdef INET6
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) addr;
        int pktinfo;
#endif
#ifdef ENABLE_NATT
        int option = -1;
#endif

        /* warn if wildcard address - should we forbid this? */
        switch (addr->sa_family) {
        case AF_INET:
                if (sin->sin_addr.s_addr == 0)
                        plog(LLV_WARNING, LOCATION, NULL,
                             "listening to wildcard address,"
                             "broadcast IKE packet may kill you\n");
                break;
#ifdef INET6
        case AF_INET6:
                if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
                        plog(LLV_DEBUG, LOCATION, NULL,
                             "ignoring multicast address %s\n",
                             saddr2str(addr));
                        return -1;
                }

                if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
                        plog(LLV_WARNING, LOCATION, NULL,
                             "listening to wildcard address, "
                             "broadcast IKE packet may kill you\n");
                break;
#endif
        default:
                plog(LLV_ERROR, LOCATION, NULL,
                     "unsupported address family %d\n",
                     addr->sa_family);
                return -1;
        }

        if ((fd = privsep_socket(addr->sa_family, SOCK_DGRAM, 0)) < 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                     "socket(%s)\n", strerror(errno));
                return -1;
        }
        close_on_exec(fd);
        if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
                plog(LLV_WARNING, LOCATION, NULL,
                     "failed to put socket in non-blocking mode\n");

        /* receive my interface address on inbound packets. */
        switch (addr->sa_family) {
        case AF_INET:
                if (setsockopt(fd, IPPROTO_IP,
#ifdef __linux__
                               IP_PKTINFO,
#else
                               IP_RECVDSTADDR,
#endif
                               (const void *) &yes, sizeof(yes)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                             "setsockopt IP_RECVDSTADDR (%s)\n",
                             strerror(errno));
                        goto err;
                }

#ifdef ENABLE_NATT
                if (udp_encap)
                        option = UDP_ENCAP_ESPINUDP;
#if defined(ENABLE_NATT_00) || defined(ENABLE_NATT_01)
                else
                        option = UDP_ENCAP_ESPINUDP_NON_IKE;
#endif
                if (option == -1)
                        break;

                if (setsockopt(fd, SOL_UDP,
                               UDP_ENCAP, &option,
                               sizeof(option)) < 0) {
                        plog(LLV_WARNING, LOCATION, NULL,
                             "setsockopt(%s): UDP_ENCAP %s\n",
                             option == UDP_ENCAP_ESPINUDP ? "UDP_ENCAP_ESPINUDP" : "UDP_ENCAP_ESPINUDP_NON_IKE",
                             strerror(errno));
                } else {
                        plog(LLV_INFO, LOCATION, NULL,
                             "%s used for NAT-T\n",
                             saddr2str(addr));
                }
#endif
                break;

#ifdef INET6
        case AF_INET6:
#if defined(INET6_ADVAPI)
#ifdef IPV6_RECVPKTINFO
                pktinfo = IPV6_RECVPKTINFO;
#else  /* old adv. API */
                pktinfo = IPV6_PKTINFO;
#endif /* IPV6_RECVPKTINFO */
#else
                pktinfo = IPV6_RECVDSTADDR;
#endif
                if (setsockopt(fd, IPPROTO_IPV6, pktinfo,
                               &yes, sizeof(yes)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                             "setsockopt IPV6_RECVDSTADDR (%d):%s\n",
                             pktinfo, strerror(errno));
                        goto err;
                }

#ifdef IPV6_USE_MIN_MTU
                if (setsockopt(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
                               &yes, sizeof(yes)) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                             "setsockopt IPV6_USE_MIN_MTU (%s)\n",
                             strerror(errno));
                        goto err;
                }
#endif
                break;
#endif
        }

        if (setsockopt(fd, SOL_SOCKET,
#ifdef __linux__
                       SO_REUSEADDR,
#else
                       SO_REUSEPORT,
#endif
                       &yes, sizeof(yes)) < 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                     "failed to set REUSE flag on %s (%s).\n",
                     saddr2str(addr), strerror(errno));
                goto err;
        }

        if (setsockopt_bypass(fd, addr->sa_family) < 0)
                goto err;

        if (privsep_bind(fd, addr, sysdep_sa_len(addr)) < 0) {
                plog(LLV_ERROR, LOCATION, addr,
                     "failed to bind to address %s (%s).\n",
                     saddr2str(addr), strerror(errno));
                goto err;
        }

        plog(LLV_INFO, LOCATION, NULL,
             "%s used as isakmp port (fd=%d)\n",
             saddr2str(addr), fd);

        monitor_fd(fd, isakmp_handler, NULL, 1);
        return fd;

err:
        close(fd);
        return -1;
}

void
isakmp_close(int fd)
{
        unmonitor_fd(fd);
        close(fd);
}

int
isakmp_send(struct ph1handle *iph1, vchar_t *sbuf)
{
        int len = 0;
        int s;
        vchar_t *vbuf = NULL, swap;

#ifdef ENABLE_NATT
        size_t extralen = NON_ESP_MARKER_USE(iph1) ? NON_ESP_MARKER_LEN : 0;

        /* Check if NON_ESP_MARKER_LEN is already there (happens when resending packets)
         */
        if(extralen == NON_ESP_MARKER_LEN &&
           *(uint32_t *)sbuf->v == 0)
                extralen = 0;

#ifdef ENABLE_FRAG
        /*
         * Do not add the non ESP marker for a packet that will
         * be fragmented. The non ESP marker should appear in
         * all fragment's packets, but not in the fragmented packet
         */
        if (iph1->frag && sbuf->l > ISAKMP_FRAG_MAXLEN)
                extralen = 0;
#endif
        if (extralen)
                plog (LLV_DEBUG, LOCATION, NULL, "Adding NON-ESP marker\n");

        /* If NAT-T port floating is in use, 4 zero bytes (non-ESP marker)
           must added just before the packet itself. For this we must
           allocate a new buffer and release it at the end. */
        if (extralen) {
                if ((vbuf = vmalloc (sbuf->l + extralen)) == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                            "vbuf allocation failed\n");
                        return -1;
                }
                *(uint32_t *)vbuf->v = 0;
                memcpy (vbuf->v + extralen, sbuf->v, sbuf->l);
                /* ensures that the modified buffer will be sent back to the caller, so
                 * add_recvdpkt() will add the correct buffer
                 */
                swap = *sbuf;
                *sbuf = *vbuf;
                *vbuf = swap;
                vfree(vbuf);
        }
#endif

        /* select the socket to be sent */
        s = myaddr_getfd(iph1->local);
        if (s == -1)
                return -1;

        plog (LLV_DEBUG, LOCATION, NULL, "%zu bytes %s\n", sbuf->l,
              saddr2str_fromto("from %s to %s", iph1->local, iph1->remote));

#ifdef ENABLE_FRAG
        if (iph1->frag && sbuf->l > ISAKMP_FRAG_MAXLEN) {
                if (isakmp_sendfrags(iph1, sbuf) == -1) {
                        plog(LLV_ERROR, LOCATION, NULL,
                            "isakmp_sendfrags failed\n");
                        return -1;
                }
        } else
#endif
        {
                len = sendfromto(s, sbuf->v, sbuf->l,
                    iph1->local, iph1->remote, lcconf->count_persend);

                if (len == -1) {
                        plog(LLV_ERROR, LOCATION, NULL, "sendfromto failed\n");
                        return -1;
                }
        }

        return 0;
}

/* called from scheduler */
static void
isakmp_ph1resend_stub(struct sched *p)
{
        struct ph1handle *iph1 = container_of(p, struct ph1handle, scr);

        if (isakmp_ph1resend(iph1) < 0) {
                remph1(iph1);
                delph1(iph1);
        }
}

static int
isakmp_ph1resend(struct ph1handle *iph1)
{
        /* Note: NEVER do the rem/del here, it will be done by the caller or by the _stub function
         */
        if (iph1->retry_counter <= 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "phase1 negotiation failed due to time up. %s\n",
                        isakmp_pindex(&iph1->index, iph1->msgid));
                /* XXX is the peer really "dead" here ??? */
                script_hook(iph1, SCRIPT_PHASE1_DEAD);
                evt_phase1(iph1, EVT_PHASE1_NO_RESPONSE, NULL);

                return -1;
        }

        if (isakmp_send(iph1, iph1->sendbuf) < 0){
                plog(LLV_ERROR, LOCATION, NULL,
                         "phase1 negotiation failed due to send error. %s\n",
                         isakmp_pindex(&iph1->index, iph1->msgid));
                evt_phase1(iph1, EVT_PHASE1_NO_RESPONSE, NULL);
                return -1;
        }

        plog(LLV_DEBUG, LOCATION, NULL,
                "resend phase1 packet %s\n",
                isakmp_pindex(&iph1->index, iph1->msgid));

        iph1->retry_counter--;

        sched_schedule(&iph1->scr, lcconf->retry_interval,
                       isakmp_ph1resend_stub);

        return 0;
}

int
isakmp_ph1send(struct ph1handle *iph1)
{
        iph1->retry_counter = lcconf->retry_counter;
        return isakmp_ph1resend(iph1);
}

/* called from scheduler */
static void
isakmp_ph2resend_stub(struct sched *p)
{
        struct ph2handle *iph2 = container_of(p, struct ph2handle, scr);

        if (isakmp_ph2resend(iph2) < 0) {
                remph2(iph2);
                delph2(iph2);
        }
}

static int
isakmp_ph2resend(struct ph2handle *iph2)
{
        /* Note: NEVER do the unbind/rem/del here, it will be done by the caller or by the _stub function
         */
        if (iph2->ph1->status >= PHASE1ST_EXPIRED) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "phase2 negotiation failed due to phase1 expired. %s\n",
                                isakmp_pindex(&iph2->ph1->index, iph2->msgid));
                return -1;
        }

        if (iph2->retry_counter <= 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "phase2 negotiation failed due to time up. %s\n",
                                isakmp_pindex(&iph2->ph1->index, iph2->msgid));
                evt_phase2(iph2, EVT_PHASE2_NO_RESPONSE, NULL);
                unbindph12(iph2);
                return -1;
        }

        if (isakmp_send(iph2->ph1, iph2->sendbuf) < 0){
                plog(LLV_ERROR, LOCATION, NULL,
                        "phase2 negotiation failed due to send error. %s\n",
                                isakmp_pindex(&iph2->ph1->index, iph2->msgid));
                evt_phase2(iph2, EVT_PHASE2_NO_RESPONSE, NULL);
                return -1;
        }

        plog(LLV_DEBUG, LOCATION, NULL,
                "resend phase2 packet %s\n",
                isakmp_pindex(&iph2->ph1->index, iph2->msgid));

        iph2->retry_counter--;

        sched_schedule(&iph2->scr, lcconf->retry_interval,
                       isakmp_ph2resend_stub);

        return 0;
}

int
isakmp_ph2send(struct ph2handle *iph2)
{
        iph2->retry_counter = lcconf->retry_counter;
        return isakmp_ph2resend(iph2);
}

/* called from scheduler */
void
isakmp_ph1dying_stub(struct sched *p)
{

        isakmp_ph1dying(container_of(p, struct ph1handle, sce));
}

void
isakmp_ph1dying(struct ph1handle *iph1)
{
        struct ph1handle *new_iph1;
        struct ph2handle *p;

        if (iph1->status >= PHASE1ST_DYING)
                return;

        /* Going away in after a while... */
        iph1->status = PHASE1ST_DYING;

        /* Any fresh phase1s? */
        new_iph1 = getph1(iph1, iph1->local, iph1->remote, 1);
        if (new_iph1 == NULL) {
                LIST_FOREACH(p, &iph1->ph2tree, ph1bind) {
                        if (p->status != PHASE2ST_ESTABLISHED)
                                continue;

                        plog(LLV_INFO, LOCATION, NULL,
                             "renegotiating phase1 to %s due to "
                             "active phase2\n",
                             saddrwop2str(iph1->remote));

                        if (iph1->side == INITIATOR)
                                isakmp_ph1begin_i(iph1->rmconf, iph1->remote,
                                                  iph1->local);

                        break;
                }
        } else {
                migrate_ph12(iph1, new_iph1);
        }

        /* Schedule for expiration */
        sched_schedule(&iph1->sce, iph1->approval->lifetime *
                       (100 - PFKEY_SOFT_LIFETIME_RATE) / 100,
                       isakmp_ph1expire_stub);
}

/* called from scheduler */
void
isakmp_ph1expire_stub(struct sched *p)
{
        isakmp_ph1expire(container_of(p, struct ph1handle, sce));
}

void
isakmp_ph1expire(struct ph1handle *iph1)
{
        char *src, *dst;

        if (iph1->status < PHASE1ST_EXPIRED) {
                src = racoon_strdup(saddr2str(iph1->local));
                dst = racoon_strdup(saddr2str(iph1->remote));
                STRDUP_FATAL(src);
                STRDUP_FATAL(dst);

                plog(LLV_INFO, LOCATION, NULL,
                         "ISAKMP-SA expired %s-%s spi:%s\n",
                         src, dst,
                         isakmp_pindex(&iph1->index, 0));
                racoon_free(src);
                racoon_free(dst);
                iph1->status = PHASE1ST_EXPIRED;
        }

        isakmp_ph1delete(iph1);
}

/* called from scheduler */
void
isakmp_ph1delete_stub(struct sched *p)
{

        isakmp_ph1delete(container_of(p, struct ph1handle, sce));
}

void
isakmp_ph1delete(struct ph1handle *iph1)
{
        struct ph2handle *p, *next;
        struct ph1handle *new_iph1;
        char *src, *dst;

        /* Migrate established phase2s. Any fresh phase1s? */
        new_iph1 = getph1(iph1, iph1->local, iph1->remote, 1);
        if (new_iph1 != NULL)
                migrate_ph12(iph1, new_iph1);

        /* Discard any left phase2s */
        for (p = LIST_FIRST(&iph1->ph2tree); p; p = next) {
                next = LIST_NEXT(p, ph1bind);
                if (p->status == PHASE2ST_ESTABLISHED)
                        isakmp_info_send_d2(p);
                /* remove all ph2 handles,
                 * as ph1handle will be expired soon
                 */
                delete_spd(p, 1);
                remph2(p);
                delph2(p);
        }

        src = racoon_strdup(saddr2str(iph1->local));
        dst = racoon_strdup(saddr2str(iph1->remote));
        STRDUP_FATAL(src);
        STRDUP_FATAL(dst);

        plog(LLV_INFO, LOCATION, NULL,
                "ISAKMP-SA deleted %s-%s spi:%s\n",
                src, dst, isakmp_pindex(&iph1->index, 0));

        evt_phase1(iph1, EVT_PHASE1_DOWN, NULL);
        if (new_iph1 == NULL && ph1_rekey_enabled(iph1))
                script_hook(iph1, SCRIPT_PHASE1_DEAD);

        racoon_free(src);
        racoon_free(dst);

        remph1(iph1);
        delph1(iph1);
}

/* called from scheduler.
 * this function will call only isakmp_ph2delete().
 * phase 2 handler remain forever if kernel doesn't cry a expire of phase 2 SA
 * by something cause.  That's why this function is called after phase 2 SA
 * expires in the userland.
 */
void
isakmp_ph2expire_stub(struct sched *p)
{

        isakmp_ph2expire(container_of(p, struct ph2handle, sce));
}

void
isakmp_ph2expire(struct ph2handle *iph2)
{
        char *src, *dst;

        src = racoon_strdup(saddrwop2str(iph2->src));
        dst = racoon_strdup(saddrwop2str(iph2->dst));
        STRDUP_FATAL(src);
        STRDUP_FATAL(dst);

        plog(LLV_INFO, LOCATION, NULL,
                "phase2 sa expired %s-%s\n", src, dst);
        racoon_free(src);
        racoon_free(dst);

        iph2->status = PHASE2ST_EXPIRED;
        sched_schedule(&iph2->sce, 1, isakmp_ph2delete_stub);
}

/* called from scheduler */
void
isakmp_ph2delete_stub(struct sched *p)
{

        isakmp_ph2delete(container_of(p, struct ph2handle, sce));
}

void
isakmp_ph2delete(struct ph2handle *iph2)
{
        char *src, *dst;

        src = racoon_strdup(saddrwop2str(iph2->src));
        dst = racoon_strdup(saddrwop2str(iph2->dst));
        STRDUP_FATAL(src);
        STRDUP_FATAL(dst);

        plog(LLV_INFO, LOCATION, NULL,
                "phase2 sa deleted %s-%s\n", src, dst);
        racoon_free(src);
        racoon_free(dst);

        remph2(iph2);
        delph2(iph2);

        return;
}

/* %%%
 * Interface between PF_KEYv2 and ISAKMP
 */
/*
 * receive ACQUIRE from kernel, and begin either phase1 or phase2.
 * if phase1 has been finished, begin phase2.
 */
int
isakmp_post_acquire(struct ph2handle *iph2, struct ph1handle *iph1hint,
    int nopassive)
{
        struct remoteconf *rmconf;
        struct ph1handle *iph1 = NULL;

        plog(LLV_DEBUG, LOCATION, NULL, "in post_acquire\n");

        /* Search appropriate configuration with masking port. Note that
         * we always use iph2->dst, and not iph2->sa_dst.
         *
         * XXX One possible need for using iph2->sa_dst if not NULL would
         * be for selecting a remote configuration based on a stable
         * address of a mobile node (not a CoA provided by MIGRATE/KMADDRESS
         * as iph2->dst hint). This scenario would require additional changes,
         * so no need to bother yet. --arno */

        if (iph1hint == NULL || iph1hint->rmconf == NULL) {
                rmconf = getrmconf(iph2->dst, nopassive ? GETRMCONF_F_NO_PASSIVE : 0);
                if (rmconf == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "no configuration found for %s.\n",
                                saddrwop2str(iph2->dst));
                        return -1;
                }
        } else {
                rmconf = iph1hint->rmconf;
        }

        /* if passive mode, ignore the acquire message */
        if (nopassive && rmconf->passive) {
                plog(LLV_DEBUG, LOCATION, NULL,
                        "because of passive mode, "
                        "ignore the acquire message for %s.\n",
                        saddrwop2str(iph2->dst));
                return -1;
        }

        /*
         * XXX Searching by IP addresses + ports might fail on
         * some cases, we should use the ISAKMP identity to search
         * matching ISAKMP.
         */
        iph1 = getph1(iph1hint, iph2->src, iph2->dst, 0);

        /* no ISAKMP-SA found. */
        if (iph1 == NULL) {
                iph2->retry_checkph1 = lcconf->retry_checkph1;
                sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub);
                plog(LLV_INFO, LOCATION, NULL,
                        "IPsec-SA request for %s queued "
                        "due to no phase1 found.\n",
                        saddrwop2str(iph2->dst));

                /* start phase 1 negotiation as a initiator. */
                if (isakmp_ph1begin_i(rmconf, iph2->dst, iph2->src) == NULL) {
                        sched_cancel(&iph2->sce);
                        return -1;
                }

                return 0;
                /*NOTREACHED*/
        }

        /* found ISAKMP-SA, but on negotiation. */
        if (iph1->status < PHASE1ST_ESTABLISHED) {
                iph2->retry_checkph1 = lcconf->retry_checkph1;
                sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub);
                plog(LLV_INFO, LOCATION, iph2->dst,
                        "request for establishing IPsec-SA was queued "
                        "due to no phase1 found.\n");
                return 0;
                /*NOTREACHED*/
        }

        /* found established ISAKMP-SA */
        /* i.e. iph1->status == PHASE1ST_ESTABLISHED */

        /* found ISAKMP-SA. */
        plog(LLV_DEBUG, LOCATION, NULL, "begin QUICK mode.\n");

        /* begin quick mode */
        if (isakmp_ph2begin_i(iph1, iph2))
                return -1;

        return 0;
}

int
isakmp_get_sainfo(struct ph2handle *iph2, struct secpolicy *sp_out,
    struct secpolicy *sp_in)
{
        struct remoteconf *conf;
        uint32_t remoteid = 0;

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

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

                idsrc = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.src,
                        sp_out->spidx.prefs, sp_out->spidx.ul_proto);
                if (idsrc == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to get ID for %s\n",
                                spidx2str(&sp_out->spidx));
                        return -1;
                }
                iddst = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.dst,
                        sp_out->spidx.prefd, sp_out->spidx.ul_proto);
                if (iddst == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to get ID for %s\n",
                                spidx2str(&sp_out->spidx));
                        vfree(idsrc);
                        return -1;
                }

                conf = getrmconf(iph2->dst, 0);
                if (conf != NULL)
                        remoteid = conf->ph1id;
                else
                        plog(LLV_DEBUG, LOCATION, NULL, "Warning: no valid rmconf !\n");

                iph2->sainfo = getsainfo(idsrc, iddst, NULL, NULL, remoteid);
                vfree(idsrc);
                vfree(iddst);
                if (iph2->sainfo == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "failed to get sainfo.\n");
                        return -1;
                        /* XXX should use the algorithm list from register message */
                }

                plog(LLV_DEBUG, LOCATION, NULL,
                        "selected sainfo: %s\n", sainfo2str(iph2->sainfo));
        }

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

        return 0;
}


/*
 * receive GETSPI from kernel.
 */
int
isakmp_post_getspi(struct ph2handle *iph2)
{
#ifdef ENABLE_STATS
        struct timeval start, end;
#endif

        /* don't process it because there is no suitable phase1-sa. */
        if (iph2->ph1->status >= PHASE1ST_EXPIRED) {
                plog(LLV_ERROR, LOCATION, iph2->ph1->remote,
                        "the negotiation is stopped, "
                        "because there is no suitable ISAKMP-SA.\n");
                return -1;
        }

#ifdef ENABLE_STATS
        gettimeofday(&start, NULL);
#endif
        if ((ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)]
                        [iph2->side]
                        [iph2->status])(iph2, NULL) != 0)
                return -1;
#ifdef ENABLE_STATS
        gettimeofday(&end, NULL);
        syslog(LOG_NOTICE, "%s(%s): %8.6f",
                "phase2",
                s_isakmp_state(ISAKMP_ETYPE_QUICK, iph2->side, iph2->status),
                timedelta(&start, &end));
#endif

        return 0;
}

/* called by scheduler */
void
isakmp_chkph1there_stub(struct sched *p)
{
        isakmp_chkph1there(container_of(p, struct ph2handle, sce));
}

void
isakmp_chkph1there(struct ph2handle *iph2)
{
        struct ph1handle *iph1;

        iph2->retry_checkph1--;
        if (iph2->retry_checkph1 < 0) {
                plog(LLV_ERROR, LOCATION, iph2->dst,
                        "phase2 negotiation failed "
                        "due to time up waiting for phase1. %s\n",
                        sadbsecas2str(iph2->src, iph2->dst,
                                iph2->satype, 0, 0));
                plog(LLV_INFO, LOCATION, NULL,
                        "delete phase 2 handler.\n");

                /* send acquire to kernel as error */
                pk_sendeacquire(iph2);

                remph2(iph2);
                delph2(iph2);

                return;
        }

        /* Search isakmp status table by address and port */
        iph1 = getph1byaddr(iph2->src, iph2->dst, 0);

        /* XXX Even if ph1 as responder is there, should we not start
         * phase 2 negotiation ? */
        if (iph1 != NULL
         && iph1->status == PHASE1ST_ESTABLISHED) {
                /* found isakmp-sa */

                plog(LLV_DEBUG2, LOCATION, NULL, "CHKPH1THERE: got a ph1 handler, setting ports.\n");
                plog(LLV_DEBUG2, LOCATION, NULL, "iph1->local: %s\n", saddr2str(iph1->local));
                plog(LLV_DEBUG2, LOCATION, NULL, "iph1->remote: %s\n", saddr2str(iph1->remote));
                plog(LLV_DEBUG2, LOCATION, NULL, "before:\n");
                plog(LLV_DEBUG2, LOCATION, NULL, "src: %s\n", saddr2str(iph2->src));
                plog(LLV_DEBUG2, LOCATION, NULL, "dst: %s\n", saddr2str(iph2->dst));
                set_port(iph2->src, extract_port(iph1->local));
                set_port(iph2->dst, extract_port(iph1->remote));
                plog(LLV_DEBUG2, LOCATION, NULL, "After:\n");
                plog(LLV_DEBUG2, LOCATION, NULL, "src: %s\n", saddr2str(iph2->src));
                plog(LLV_DEBUG2, LOCATION, NULL, "dst: %s\n", saddr2str(iph2->dst));

                /* begin quick mode */
                (void)isakmp_ph2begin_i(iph1, iph2);
                return;
        }

        plog(LLV_DEBUG2, LOCATION, NULL, "CHKPH1THERE: no established ph1 handler found\n");

        /* no isakmp-sa found */
        sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub);

        return;
}

/* copy variable data into ALLOCATED buffer. */
caddr_t
isakmp_set_attr_v(caddr_t buf, int type, caddr_t val, int len)
{
        struct isakmp_data *data;

        data = (struct isakmp_data *)buf;
        data->type = htons((uint16_t)type | ISAKMP_GEN_TLV);
        data->lorv = htons((uint16_t)len);
        memcpy(data + 1, val, len);

        return buf + sizeof(*data) + len;
}

/* copy fixed length data into ALLOCATED buffer. */
caddr_t
isakmp_set_attr_l(caddr_t buf, int type, uint32_t val)
{
        struct isakmp_data *data;

        data = (struct isakmp_data *)buf;
        data->type = htons((uint16_t)type | ISAKMP_GEN_TV);
        data->lorv = htons((uint16_t)val);

        return buf + sizeof(*data);
}

/* add a variable data attribute to the buffer by reallocating it. */
vchar_t *
isakmp_add_attr_v(vchar_t *buf0, int type, caddr_t val, int len)
{
        vchar_t *buf = NULL;
        struct isakmp_data *data;
        size_t tlen;
        size_t oldlen = 0;

        tlen = sizeof(*data) + len;

        if (buf0) {
                oldlen = buf0->l;
                buf = vrealloc(buf0, oldlen + tlen);
        } else
                buf = vmalloc(tlen);
        if (!buf) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get a attribute buffer.\n");
                return NULL;
        }

        data = (struct isakmp_data *)(buf->v + oldlen);
        data->type = htons((uint16_t)type | ISAKMP_GEN_TLV);
        data->lorv = htons((uint16_t)len);
        memcpy(data + 1, val, len);

        return buf;
}

/* add a fixed data attribute to the buffer by reallocating it. */
vchar_t *
isakmp_add_attr_l(vchar_t *buf0, int type, uint32_t val)
{
        vchar_t *buf = NULL;
        struct isakmp_data *data;
        size_t tlen;
        size_t oldlen = 0;

        tlen = sizeof(*data);

        if (buf0) {
                oldlen = buf0->l;
                buf = vrealloc(buf0, oldlen + tlen);
        } else
                buf = vmalloc(tlen);
        if (!buf) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get a attribute buffer.\n");
                return NULL;
        }

        data = (struct isakmp_data *)(buf->v + oldlen);
        data->type = htons((uint16_t)type | ISAKMP_GEN_TV);
        data->lorv = htons((uint16_t)val);

        return buf;
}

/*
 * calculate cookie and set.
 */
int
isakmp_newcookie(caddr_t place, struct sockaddr *remote, struct sockaddr *local)
{
        vchar_t *buf = NULL, *buf2 = NULL;
        char *p;
        size_t blen;
        size_t alen;
        caddr_t sa1, sa2;
        time_t t;
        int error = -1;
        u_short port;


        if (remote->sa_family != local->sa_family) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "address family mismatch, remote:%d local:%d\n",
                        remote->sa_family, local->sa_family);
                goto end;
        }
        switch (remote->sa_family) {
        case AF_INET:
                alen = sizeof(struct in_addr);
                sa1 = (caddr_t)&((struct sockaddr_in *)remote)->sin_addr;
                sa2 = (caddr_t)&((struct sockaddr_in *)local)->sin_addr;
                break;
#ifdef INET6
        case AF_INET6:
                alen = sizeof(struct in6_addr);
                sa1 = (caddr_t)&((struct sockaddr_in6 *)remote)->sin6_addr;
                sa2 = (caddr_t)&((struct sockaddr_in6 *)local)->sin6_addr;
                break;
#endif
        default:
                plog(LLV_ERROR, LOCATION, NULL,
                        "invalid family: %d\n", remote->sa_family);
                goto end;
        }
        blen = (alen + sizeof(u_short)) * 2
                + sizeof(time_t) + lcconf->secret_size;
        buf = vmalloc(blen);
        if (buf == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get a cookie.\n");
                goto end;
        }
        p = buf->v;

        /* copy my address */
        memcpy(p, sa1, alen);
        p += alen;
        port = ((struct sockaddr_in *)remote)->sin_port;
        memcpy(p, &port, sizeof(u_short));
        p += sizeof(u_short);

        /* copy target address */
        memcpy(p, sa2, alen);
        p += alen;
        port = ((struct sockaddr_in *)local)->sin_port;
        memcpy(p, &port, sizeof(u_short));
        p += sizeof(u_short);

        /* copy time */
        t = time(0);
        memcpy(p, (caddr_t)&t, sizeof(t));
        p += sizeof(t);

        /* copy random value */
        buf2 = eay_set_random(lcconf->secret_size);
        if (buf2 == NULL)
                goto end;
        memcpy(p, buf2->v, lcconf->secret_size);
        p += lcconf->secret_size;
        vfree(buf2);

        buf2 = eay_sha1_one(buf);
        memcpy(place, buf2->v, sizeof(cookie_t));

        sa1 = val2str(place, sizeof (cookie_t));
        plog(LLV_DEBUG, LOCATION, NULL, "new cookie:\n%s\n", sa1);
        racoon_free(sa1);

        error = 0;
end:
        if (buf != NULL)
                vfree(buf);
        if (buf2 != NULL)
                vfree(buf2);
        return error;
}

/*
 * save partner's(payload) data into phhandle.
 */
int
isakmp_p2ph( vchar_t **buf, struct isakmp_gen *gen)
{
        /* XXX to be checked in each functions for logging. */
        if (*buf) {
                plog(LLV_WARNING, LOCATION, NULL,
                        "ignore this payload, same payload type exist.\n");
                return -1;
        }

        *buf = vmalloc(ntohs(gen->len) - sizeof(*gen));
        if (*buf == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get buffer.\n");
                return -1;
        }
        memcpy((*buf)->v, gen + 1, (*buf)->l);

        return 0;
}

uint32_t
isakmp_newmsgid2(struct ph1handle *iph1)
{
        uint32_t msgid2;

        do {
                msgid2 = eay_random();
        } while (getph2bymsgid(iph1, msgid2));

        return msgid2;
}

/*
 * set values into allocated buffer of isakmp header for phase 1
 */
static caddr_t
set_isakmp_header(vchar_t *vbuf, struct ph1handle *iph1, int nptype,
    uint8_t etype, uint8_t flags, uint32_t msgid)
{
        struct isakmp *isakmp;

        if (vbuf->l < sizeof(*isakmp))
                return NULL;

        isakmp = (struct isakmp *)vbuf->v;

        memcpy(&isakmp->i_ck, &iph1->index.i_ck, sizeof(cookie_t));
        memcpy(&isakmp->r_ck, &iph1->index.r_ck, sizeof(cookie_t));
        isakmp->np = nptype;
        isakmp->v = iph1->version;
        isakmp->etype = etype;
        isakmp->flags = flags;
        isakmp->msgid = msgid;
        isakmp->len = htonl(vbuf->l);

        return vbuf->v + sizeof(*isakmp);
}

/*
 * set values into allocated buffer of isakmp header for phase 1
 */
caddr_t
set_isakmp_header1(vchar_t *vbuf, struct ph1handle *iph1, int nptype)
{
        return set_isakmp_header (vbuf, iph1, nptype, iph1->etype, iph1->flags, iph1->msgid);
}

/*
 * set values into allocated buffer of isakmp header for phase 2
 */
caddr_t
set_isakmp_header2(vchar_t *vbuf, struct ph2handle *iph2, int nptype)
{
        return set_isakmp_header (vbuf, iph2->ph1, nptype, ISAKMP_ETYPE_QUICK, iph2->flags, iph2->msgid);
}

/*
 * set values into allocated buffer of isakmp payload.
 */
caddr_t
set_isakmp_payload(caddr_t buf, vchar_t *src, int nptype)
{
        struct isakmp_gen *gen;
        caddr_t p = buf;

        plog(LLV_DEBUG, LOCATION, NULL, "add payload of len %zu, next type %d\n",
            src->l, nptype);

        gen = (struct isakmp_gen *)p;
        gen->np = nptype;
        gen->len = htons(sizeof(*gen) + src->l);
        p += sizeof(*gen);
        memcpy(p, src->v, src->l);
        p += src->l;

        return p;
}

static int
etypesw1(int etype)
{
        switch (etype) {
        case ISAKMP_ETYPE_IDENT:
                return 1;
        case ISAKMP_ETYPE_AGG:
                return 2;
        case ISAKMP_ETYPE_BASE:
                return 3;
        default:
                return 0;
        }
        /*NOTREACHED*/
}

static int
etypesw2(int etype)
{
        switch (etype) {
        case ISAKMP_ETYPE_QUICK:
                return 1;
        default:
                return 0;
        }
        /*NOTREACHED*/
}

#ifdef HAVE_PRINT_ISAKMP_C
/* for print-isakmp.c */
char *snapend;
extern void isakmp_print(const u_char *, u_int, const u_char *);

char *getname(const u_char *);
#ifdef INET6
char *getname6(const u_char *);
#endif
int safeputchar(int);

/*
 * Return a name for the IP address pointed to by ap.  This address
 * is assumed to be in network byte order.
 */
char *
getname(ap)
        const u_char *ap;
{
        struct sockaddr_in addr;
        static char ntop_buf[NI_MAXHOST];

        memset(&addr, 0, sizeof(addr));
#ifndef __linux__
        addr.sin_len = sizeof(struct sockaddr_in);
#endif
        addr.sin_family = AF_INET;
        memcpy(&addr.sin_addr, ap, sizeof(addr.sin_addr));
        if (getnameinfo((struct sockaddr *)&addr, sizeof(addr),
                        ntop_buf, sizeof(ntop_buf), NULL, 0,
                        NI_NUMERICHOST | niflags))
                strlcpy(ntop_buf, "?", sizeof(ntop_buf));

        return ntop_buf;
}

#ifdef INET6
/*
 * Return a name for the IP6 address pointed to by ap.  This address
 * is assumed to be in network byte order.
 */
char *
getname6(ap)
        const u_char *ap;
{
        struct sockaddr_in6 addr;
        static char ntop_buf[NI_MAXHOST];

        memset(&addr, 0, sizeof(addr));
        addr.sin6_len = sizeof(struct sockaddr_in6);
        addr.sin6_family = AF_INET6;
        memcpy(&addr.sin6_addr, ap, sizeof(addr.sin6_addr));
        if (getnameinfo((struct sockaddr *)&addr, addr.sin6_len,
                        ntop_buf, sizeof(ntop_buf), NULL, 0,
                        NI_NUMERICHOST | niflags))
                strlcpy(ntop_buf, "?", sizeof(ntop_buf));

        return ntop_buf;
}
#endif /* INET6 */

int
safeputchar(c)
        int c;
{
        unsigned char ch;

        ch = (unsigned char)(c & 0xff);
        if (c < 0x80 && isprint(c))
                return printf("%c", c & 0xff);
        else
                return printf("\\%03o", c & 0xff);
}

void
isakmp_printpacket(msg, from, my, decoded)
        vchar_t *msg;
        struct sockaddr *from;
        struct sockaddr *my;
        int decoded;
{
#ifdef YIPS_DEBUG
        struct timeval tv;
        int s;
        char hostbuf[NI_MAXHOST];
        char portbuf[NI_MAXSERV];
        struct isakmp *isakmp;
        vchar_t *buf;
#endif

        if (loglevel < LLV_DEBUG)
                return;

#ifdef YIPS_DEBUG
        plog(LLV_DEBUG, LOCATION, NULL, "begin.\n");

        gettimeofday(&tv, NULL);
        s = tv.tv_sec % 3600;
        printf("%02d:%02d.%06u ", s / 60, s % 60, (uint32_t)tv.tv_usec);

        if (from) {
                if (getnameinfo(from, sysdep_sa_len(from), hostbuf, sizeof(hostbuf),
                                portbuf, sizeof(portbuf),
                                NI_NUMERICHOST | NI_NUMERICSERV | niflags)) {
                        strlcpy(hostbuf, "?", sizeof(hostbuf));
                        strlcpy(portbuf, "?", sizeof(portbuf));
                }
                printf("%s:%s", hostbuf, portbuf);
        } else
                printf("?");
        printf(" -> ");
        if (my) {
                if (getnameinfo(my, sysdep_sa_len(my), hostbuf, sizeof(hostbuf),
                                portbuf, sizeof(portbuf),
                                NI_NUMERICHOST | NI_NUMERICSERV | niflags)) {
                        strlcpy(hostbuf, "?", sizeof(hostbuf));
                        strlcpy(portbuf, "?", sizeof(portbuf));
                }
                printf("%s:%s", hostbuf, portbuf);
        } else
                printf("?");
        printf(": ");

        buf = vdup(msg);
        if (!buf) {
                printf("(malloc fail)\n");
                return;
        }
        if (decoded) {
                isakmp = (struct isakmp *)buf->v;
                if (isakmp->flags & ISAKMP_FLAG_E) {
#if 0
                        int pad;
                        pad = *(u_char *)(buf->v + buf->l - 1);
                        if (buf->l < pad && 2 < vflag)
                                printf("(wrong padding)");
#endif
                        isakmp->flags &= ~ISAKMP_FLAG_E;
                }
        }

        snapend = buf->v + buf->l;
        isakmp_print(buf->v, buf->l, NULL);
        vfree(buf);
        printf("\n");
        fflush(stdout);

        return;
#endif
}
#endif /*HAVE_PRINT_ISAKMP_C*/

int
copy_ph1addresses(struct ph1handle *iph1, struct remoteconf *rmconf,
    struct sockaddr *remote, struct sockaddr *local)
{
        uint16_t port = 0;

        /* address portion must be grabbed from real remote address "remote" */
        iph1->remote = dupsaddr(remote);
        if (iph1->remote == NULL)
                return -1;

        /*
         * if remote has no port # (in case of initiator - from ACQUIRE msg)
         * - if remote.conf specifies port #, use that
         * - if remote.conf does not, use lcconf->port_isakmp
         * if remote has port # (in case of responder - from recvfrom(2))
         * respect content of "remote".
         */
        if (extract_port(iph1->remote) == 0) {
                port = 0;
                if (rmconf != NULL)
                        port = extract_port(rmconf->remote);
                if (port == 0)
                        port = lcconf->port_isakmp;
                set_port(iph1->remote, port);
        }

        if (local == NULL)
                iph1->local = getlocaladdr(iph1->remote);
        else
                iph1->local = dupsaddr(local);
        if (iph1->local == NULL)
                return -1;

        if (extract_port(iph1->local) == 0) {
                port = myaddr_getsport(iph1->local);
                if (port == 0)
                        port = PORT_ISAKMP;
                set_port(iph1->local, port);
        }

#ifdef ENABLE_NATT
        if (extract_port(iph1->local) == lcconf->port_isakmp_natt) {
                plog(LLV_DEBUG, LOCATION, NULL, "Marking ports as changed\n");
                iph1->natt_flags |= NAT_ADD_NON_ESP_MARKER;
        }
#endif

        return 0;
}

/*ARGSUSED*/
static int
nostate1(struct ph1handle *iph1, vchar_t *msg __unused)
{
        plog(LLV_ERROR, LOCATION, iph1->remote, "wrong state %u.\n",
                        iph1->status);
        return -1;
}

/*ARGSUSED*/
static int
nostate2(struct ph2handle *iph2, vchar_t *msg __unused)
{
        plog(LLV_ERROR, LOCATION, iph2->ph1->remote, "wrong state %u.\n",
                iph2->status);
        return -1;
}

void
log_ph1established(const struct ph1handle *iph1)
{
        char *src, *dst;

        src = racoon_strdup(saddr2str(iph1->local));
        dst = racoon_strdup(saddr2str(iph1->remote));
        STRDUP_FATAL(src);
        STRDUP_FATAL(dst);

        plog(LLV_INFO, LOCATION, NULL,
                "ISAKMP-SA established %s-%s spi:%s\n",
                src, dst,
                isakmp_pindex(&iph1->index, 0));

        evt_phase1(iph1, EVT_PHASE1_UP, NULL);
        if(!iph1->rmconf->mode_cfg)
                evt_phase1(iph1, EVT_PHASE1_MODE_CFG, NULL);

        racoon_free(src);
        racoon_free(dst);

        return;
}

struct payload_list *
isakmp_plist_append_full (struct payload_list *plist, vchar_t *payload,
                          uint8_t payload_type, uint8_t free_payload)
{
        if (! plist) {
                plist = racoon_malloc (sizeof (struct payload_list));
                plist->prev = NULL;
        }
        else {
                plist->next = racoon_malloc (sizeof (struct payload_list));
                plist->next->prev = plist;
                plist = plist->next;
        }

        plist->next = NULL;
        plist->payload = payload;
        plist->payload_type = payload_type;
        plist->free_payload = free_payload;

        return plist;
}

vchar_t *
isakmp_plist_set_all (struct payload_list **plist, struct ph1handle *iph1)
{
        struct payload_list *ptr = *plist, *first;
        size_t tlen = sizeof (struct isakmp), n = 0;
        vchar_t *buf = NULL;
        char *p;

        /* Seek to the first item.  */
        while (ptr->prev) ptr = ptr->prev;
        first = ptr;

        /* Compute the whole length.  */
        while (ptr) {
                tlen += ptr->payload->l + sizeof (struct isakmp_gen);
                ptr = ptr->next;
        }

        buf = vmalloc(tlen);
        if (buf == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "failed to get buffer to send.\n");
                goto end;
        }

        ptr = first;

        p = set_isakmp_header1(buf, iph1, ptr->payload_type);
        if (p == NULL)
                goto end;

        while (ptr)
        {
                p = set_isakmp_payload (p, ptr->payload, ptr->next ? ptr->next->payload_type : ISAKMP_NPTYPE_NONE);
                first = ptr;
                ptr = ptr->next;
                if (first->free_payload)
                        vfree(first->payload);
                racoon_free (first);
                /* ptr->prev = NULL; first = NULL; ... omitted.  */
                n++;
        }

        *plist = NULL;

        return buf;
end:
        if (buf != NULL)
                vfree(buf);
        return NULL;
}

#ifdef ENABLE_FRAG
int
frag_handler(struct ph1handle *iph1, vchar_t *msg, struct sockaddr *remote,
    struct sockaddr *local)
{
        vchar_t *newmsg;

        if (isakmp_frag_extract(iph1, msg) == 1) {
                if ((newmsg = isakmp_frag_reassembly(iph1)) == NULL) {
                        plog(LLV_ERROR, LOCATION, remote,
                            "Packet reassembly failed\n");
                        return -1;
                }
                return isakmp_main(newmsg, remote, local);
        }

        return 0;
}
#endif

void
script_hook(struct ph1handle *iph1, int script)
{
#define IP_MAX 40
#define PORT_MAX 6
        char addrstr[IP_MAX];
        char portstr[PORT_MAX];
        char **envp = NULL;
        int envc = 1;
        char **c;

        if (iph1 == NULL ||
                iph1->rmconf == NULL ||
                iph1->rmconf->script[script] == NULL)
                return;

#ifdef ENABLE_HYBRID
        (void)isakmp_cfg_setenv(iph1, &envp, &envc);
#endif

        /* local address */
        GETNAMEINFO(iph1->local, addrstr, portstr);

        if (script_env_append(&envp, &envc, "LOCAL_ADDR", addrstr) != 0) {
                plog(LLV_ERROR, LOCATION, NULL, "Cannot set LOCAL_ADDR\n");
                goto out;
        }

        if (script_env_append(&envp, &envc, "LOCAL_PORT", portstr) != 0) {
                plog(LLV_ERROR, LOCATION, NULL, "Cannot set LOCAL_PORT\n");
                goto out;
        }

        /* Peer address */
        if (iph1->remote != NULL) {
                GETNAMEINFO(iph1->remote, addrstr, portstr);

                if (script_env_append(&envp, &envc, 
                    "REMOTE_ADDR", addrstr) != 0) {
                        plog(LLV_ERROR, LOCATION, NULL, 
                            "Cannot set REMOTE_ADDR\n");
                        goto out;
                }

                if (script_env_append(&envp, &envc, 
                    "REMOTE_PORT", portstr) != 0) {
                        plog(LLV_ERROR, LOCATION, NULL, 
                            "Cannot set REMOTEL_PORT\n");
                        goto out;
                }
        }

        /* Peer identity. */
        if (iph1->id_p != NULL) {
                if (script_env_append(&envp, &envc, "REMOTE_ID",
                                      ipsecdoi_id2str(iph1->id_p)) != 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                             "Cannot set REMOTE_ID\n");
                        goto out;
                }
        }

        if (privsep_script_exec(iph1->rmconf->script[script]->v, 
            script, envp) != 0) 
                plog(LLV_ERROR, LOCATION, NULL, 
                    "Script %s execution failed\n", script_names[script]);

out:
        for (c = envp; *c; c++)
                racoon_free(*c);

        racoon_free(envp);

        return;
}

int
script_env_append(char ***envp, int *envc, const char *name, char *value)
{
        char *envitem;
        char **newenvp;
        int newenvc;

        envitem = racoon_malloc(strlen(name) + 1 + strlen(value) + 1);
        if (envitem == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                    "Cannot allocate memory: %s\n", strerror(errno));
                return -1;
        }
        sprintf(envitem, "%s=%s", name, value);

        newenvc = (*envc) + 1;
        newenvp = racoon_realloc(*envp, newenvc * sizeof(char *));
        if (newenvp == NULL) {
                plog(LLV_ERROR, LOCATION, NULL,
                    "Cannot allocate memory: %s\n", strerror(errno));
                racoon_free(envitem);
                return -1;
        }

        newenvp[newenvc - 2] = envitem;
        newenvp[newenvc - 1] = NULL;

        *envp = newenvp;
        *envc = newenvc;
        return 0;
}

int
script_exec(char *script, int name, char *const envp[])
{
        char *argv[] = { NULL, NULL, NULL };

        argv[0] = script;
        argv[1] = __UNCONST(script_names[name]);
        argv[2] = NULL;

        switch (fork()) {
        case 0:
                execve(argv[0], argv, envp);
                plog(LLV_ERROR, LOCATION, NULL,
                    "execve(\"%s\") failed: %s\n",
                    argv[0], strerror(errno));
                _exit(1);
        case -1:
                plog(LLV_ERROR, LOCATION, NULL,
                    "Cannot fork: %s\n", strerror(errno));
                return -1;
        default:
                break;
        }
        return 0;

}

void
purge_remote(struct ph1handle *iph1)
{
        vchar_t *buf = NULL;
        struct sadb_msg *msg, *next, *end;
        struct sadb_sa *sa;
        struct sockaddr *src, *dst;
        caddr_t mhp[SADB_EXT_MAX + 1];
        u_int proto_id;
        struct ph2handle *iph2;
        struct ph1handle *new_iph1;

        plog(LLV_INFO, LOCATION, NULL,
                 "purging ISAKMP-SA spi=%s.\n",
                 isakmp_pindex(&(iph1->index), iph1->msgid));

        /* Mark as expired. */
        iph1->status = PHASE1ST_EXPIRED;

        /* Check if we have another, still valid, phase1 SA. */
        new_iph1 = getph1(iph1, iph1->local, iph1->remote, GETPH1_F_ESTABLISHED);

        /*
         * Delete all orphaned or binded to the deleting ph1handle phase2 SAs.
         * Keep all others phase2 SAs.
         */
        buf = pfkey_dump_sadb(SADB_SATYPE_UNSPEC);
        if (buf == NULL) {
                plog(LLV_DEBUG, LOCATION, NULL,
                        "pfkey_dump_sadb returned nothing.\n");
                return;
        }

        msg = (struct sadb_msg *)buf->v;
        end = (struct sadb_msg *)(buf->v + buf->l);

        while (msg < end) {
                if ((msg->sadb_msg_len << 3) < sizeof(*msg))
                        break;
                next = (struct sadb_msg *)((caddr_t)msg + (msg->sadb_msg_len << 3));
                if (msg->sadb_msg_type != SADB_DUMP) {
                        msg = next;
                        continue;
                }

                if (pfkey_align(msg, mhp) || pfkey_check(mhp)) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                "pfkey_check (%s)\n", ipsec_strerror());
                        msg = next;
                        continue;
                }

                sa = (struct sadb_sa *)(mhp[SADB_EXT_SA]);
                if (!sa ||
                    !mhp[SADB_EXT_ADDRESS_SRC] ||
                    !mhp[SADB_EXT_ADDRESS_DST]) {
                        msg = next;
                        continue;
                }
                pk_fixup_sa_addresses(mhp);
                src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
                dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

                if (sa->sadb_sa_state != SADB_SASTATE_LARVAL &&
                    sa->sadb_sa_state != SADB_SASTATE_MATURE &&
                    sa->sadb_sa_state != SADB_SASTATE_DYING) {
                        msg = next;
                        continue;
                }

                /*
                 * check in/outbound SAs.
                 * Select only SAs where src == local and dst == remote (outgoing)
                 * or src == remote and dst == local (incoming).
                 */
                if ((cmpsaddr(iph1->local, src) != CMPSADDR_MATCH ||
                     cmpsaddr(iph1->remote, dst) != CMPSADDR_MATCH) &&
                    (cmpsaddr(iph1->local, dst) != CMPSADDR_MATCH ||
                     cmpsaddr(iph1->remote, src) != CMPSADDR_MATCH)) {
                        msg = next;
                        continue;
                }

                proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
                iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi);

                /* Check if there is another valid ISAKMP-SA */
                if (new_iph1 != NULL) {

                        if (iph2 == NULL) {
                                /* No handler... still send a pfkey_delete message, but log this !*/
                                plog(LLV_INFO, LOCATION, NULL,
                                        "Unknown IPsec-SA spi=%u, hmmmm?\n",
                                        ntohl(sa->sadb_sa_spi));
                        }else{

                                /*
                                 * If we have a new ph1, do not purge IPsec-SAs binded
                                 *  to a different ISAKMP-SA
                                 */
                                if (iph2->ph1 != NULL && iph2->ph1 != iph1){
                                        msg = next;
                                        continue;
                                }

                                /* If the ph2handle is established, do not purge IPsec-SA */
                                if (iph2->status == PHASE2ST_ESTABLISHED ||
                                        iph2->status == PHASE2ST_EXPIRED) {

                                        plog(LLV_INFO, LOCATION, NULL,
                                                 "keeping IPsec-SA spi=%u - found valid ISAKMP-SA spi=%s.\n",
                                                 ntohl(sa->sadb_sa_spi),
                                                 isakmp_pindex(&(new_iph1->index), new_iph1->msgid));
                                        msg = next;
                                        continue;
                                }
                        }
                }


                pfkey_send_delete(lcconf->sock_pfkey,
                                  msg->sadb_msg_satype,
                                  IPSEC_MODE_ANY,
                                  src, dst, sa->sadb_sa_spi);

                /* delete a relative phase 2 handle. */
                if (iph2 != NULL) {
                        delete_spd(iph2, 0);
                        remph2(iph2);
                        delph2(iph2);
                }

                plog(LLV_INFO, LOCATION, NULL,
                         "purged IPsec-SA spi=%u.\n",
                         ntohl(sa->sadb_sa_spi));

                msg = next;
        }

        if (buf)
                vfree(buf);

        /* Mark the phase1 handler as EXPIRED */
        plog(LLV_INFO, LOCATION, NULL,
                 "purged ISAKMP-SA spi=%s.\n",
                 isakmp_pindex(&(iph1->index), iph1->msgid));

        isakmp_ph1delete(iph1);
}

void
delete_spd(struct ph2handle *iph2, u_int64_t created)
{
        struct policyindex spidx;
        struct sockaddr_storage addr;
        uint8_t pref;
        struct sockaddr *src;
        struct sockaddr *dst;
        int error;
        int idi2type = 0;/* switch whether copy IDs into id[src,dst]. */

        if (iph2 == NULL)
                return;

        /* Delete the SPD entry if we generated it
         */
        if (! iph2->generated_spidx )
                return;

        src = iph2->src;
        dst = iph2->dst;

        plog(LLV_INFO, LOCATION, NULL,
                 "deleting a generated policy.\n");

        memset(&spidx, 0, sizeof(spidx));
        iph2->spidx_gen = (caddr_t )&spidx;

        /* make inbound policy */
        iph2->src = dst;
        iph2->dst = src;
        spidx.dir = IPSEC_DIR_INBOUND;
        spidx.ul_proto = 0;

        /*
         * Note: code from get_proposal_r
         */

#define _XIDT(d) ((struct ipsecdoi_id_b *)(d)->v)->type

        /*
         * make destination address in spidx from either ID payload
         * or phase 1 address into a address in spidx.
         */
        if (iph2->id != NULL
                && (_XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR
                        || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR
                        || _XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR_SUBNET
                        || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR_SUBNET)) {
                /* get a destination address of a policy */
                error = ipsecdoi_id2sockaddr(iph2->id,
                                                                         (struct sockaddr *)&spidx.dst,
                                                                         &spidx.prefd, &spidx.ul_proto);
                if (error)
                        goto purge;

#ifdef INET6
                /*
                 * get scopeid from the SA address.
                 * note that the phase 1 source address is used as
                 * a destination address to search for a inbound
                 * policy entry because rcoon is responder.
                 */
                if (_XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR) {
                        if ((error =
                                 setscopeid((struct sockaddr *)&spidx.dst,
                                                        iph2->src)) != 0)
                                goto purge;
                }
#endif

                if (_XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR
                        || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR)
                        idi2type = _XIDT(iph2->id);

        } else {

                plog(LLV_DEBUG, LOCATION, NULL,
                         "get a destination address of SP index "
                         "from phase1 address "
                         "due to no ID payloads found "
                         "OR because ID type is not address.\n");

                /*
                 * copy the SOURCE address of IKE into the
                 * DESTINATION address of the key to search the
                 * SPD because the direction of policy is inbound.
                 */
                memcpy(&spidx.dst, iph2->src, sysdep_sa_len(iph2->src));
                switch (spidx.dst.ss_family) {
                case AF_INET:
                        spidx.prefd =
                                sizeof(struct in_addr) << 3;
                        break;
#ifdef INET6
                case AF_INET6:
                        spidx.prefd =
                                sizeof(struct in6_addr) << 3;
                        break;
#endif
                default:
                        spidx.prefd = 0;
                        break;
                }
        }

                /* make source address in spidx */
        if (iph2->id_p != NULL
                && (_XIDT(iph2->id_p) == IPSECDOI_ID_IPV4_ADDR
                        || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR
                        || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV4_ADDR_SUBNET
                        || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR_SUBNET)) {
                /* get a source address of inbound SA */
                error = ipsecdoi_id2sockaddr(iph2->id_p,
                                             (struct sockaddr *)&spidx.src,
                                             &spidx.prefs, &spidx.ul_proto);
                if (error)
                        goto purge;

#ifdef INET6
                /*
                 * get scopeid from the SA address.
                 * for more detail, see above of this function.
                 */
                if (_XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR) {
                        error =
                                setscopeid((struct sockaddr *)&spidx.src,
                                                   iph2->dst);
                        if (error)
                                goto purge;
                }
#endif

                /* make sa_[src,dst] if both ID types are IP address and same */
                if (_XIDT(iph2->id_p) == idi2type
                        && spidx.dst.ss_family == spidx.src.ss_family) {
                        iph2->sa_src =
                                dupsaddr((struct sockaddr *)&spidx.dst);
                        if (iph2->sa_src == NULL) {
                                plog(LLV_ERROR, LOCATION, NULL,
                                         "allocation failed\n");
                                goto purge;
                        }
                        iph2->sa_dst =
                                dupsaddr((struct sockaddr *)&spidx.src);
                        if (iph2->sa_dst == NULL) {
                                plog(LLV_ERROR, LOCATION, NULL,
                                         "allocation failed\n");
                                goto purge;
                        }
                }

        } else {
                plog(LLV_DEBUG, LOCATION, NULL,
                         "get a source address of SP index "
                         "from phase1 address "
                         "due to no ID payloads found "
                         "OR because ID type is not address.\n");

                /* see above comment. */
                memcpy(&spidx.src, iph2->dst, sysdep_sa_len(iph2->dst));
                switch (spidx.src.ss_family) {
                case AF_INET:
                        spidx.prefs =
                                sizeof(struct in_addr) << 3;
                        break;
#ifdef INET6
                case AF_INET6:
                        spidx.prefs =
                                sizeof(struct in6_addr) << 3;
                        break;
#endif
                default:
                        spidx.prefs = 0;
                        break;
                }
        }

#undef _XIDT

        plog(LLV_DEBUG, LOCATION, NULL,
                 "get a src address from ID payload "
                 "%s prefixlen=%u ul_proto=%u\n",
                 saddr2str((struct sockaddr *)&spidx.src),
                 spidx.prefs, spidx.ul_proto);
        plog(LLV_DEBUG, LOCATION, NULL,
                 "get dst address from ID payload "
                 "%s prefixlen=%u ul_proto=%u\n",
                 saddr2str((struct sockaddr *)&spidx.dst),
                 spidx.prefd, spidx.ul_proto);

        /*
         * convert the ul_proto if it is 0
         * because 0 in ID payload means a wild card.
         */
        if (spidx.ul_proto == 0)
                spidx.ul_proto = IPSEC_ULPROTO_ANY;

#undef _XIDT

        /* Check if the generated SPD has the same timestamp as the SA.
         * If timestamps are different, this means that the SPD entry has been
         * refreshed by another SA, and should NOT be deleted with the current SA.
         */
        if( created ){
                struct secpolicy *p;

                p = getsp(&spidx);
                if(p != NULL){
                        /* just do no test if p is NULL, because this probably just means
                         * that the policy has already be deleted for some reason.
                         */
                        if(p->spidx.created != created)
                                goto purge;
                }
        }

        /* End of code from get_proposal_r
         */

        if (pk_sendspddelete(iph2) < 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                         "pfkey spddelete(inbound) failed.\n");
        }else{
                plog(LLV_DEBUG, LOCATION, NULL,
                         "pfkey spddelete(inbound) sent.\n");
        }

#ifdef HAVE_POLICY_FWD
        /* make forward policy if required */
        if (tunnel_mode_prop(iph2->approval)) {
                spidx.dir = IPSEC_DIR_FWD;
                if (pk_sendspddelete(iph2) < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                                 "pfkey spddelete(forward) failed.\n");
                }else{
                        plog(LLV_DEBUG, LOCATION, NULL,
                                 "pfkey spddelete(forward) sent.\n");
                }
        }
#endif

        /* make outbound policy */
        iph2->src = src;
        iph2->dst = dst;
        spidx.dir = IPSEC_DIR_OUTBOUND;
        addr = spidx.src;
        spidx.src = spidx.dst;
        spidx.dst = addr;
        pref = spidx.prefs;
        spidx.prefs = spidx.prefd;
        spidx.prefd = pref;

        if (pk_sendspddelete(iph2) < 0) {
                plog(LLV_ERROR, LOCATION, NULL,
                         "pfkey spddelete(outbound) failed.\n");
        }else{
                plog(LLV_DEBUG, LOCATION, NULL,
                         "pfkey spddelete(outbound) sent.\n");
        }
purge:
        iph2->spidx_gen=NULL;
}


#ifdef INET6
uint32_t
setscopeid(struct sockaddr *sp_addr0, struct sockaddr *sa_addr0)
{
        struct sockaddr_in6 *sp_addr, *sa_addr;

        sp_addr = (struct sockaddr_in6 *)sp_addr0;
        sa_addr = (struct sockaddr_in6 *)sa_addr0;

        if (!IN6_IS_ADDR_LINKLOCAL(&sp_addr->sin6_addr)
         && !IN6_IS_ADDR_SITELOCAL(&sp_addr->sin6_addr)
         && !IN6_IS_ADDR_MULTICAST(&sp_addr->sin6_addr))
                return 0;

        /* this check should not be here ? */
        if (sa_addr->sin6_family != AF_INET6) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "can't get scope ID: family mismatch\n");
                return (uint32_t)-1;
        }

        if (!IN6_IS_ADDR_LINKLOCAL(&sa_addr->sin6_addr)) {
                plog(LLV_ERROR, LOCATION, NULL,
                        "scope ID is not supported except of lladdr.\n");
                return (uint32_t)-1;
        }

        sp_addr->sin6_scope_id = sa_addr->sin6_scope_id;

        return 0;
}
#endif