root/sbin/isakmpd/pf_key_v2.c
/* $OpenBSD: pf_key_v2.c,v 1.205 2023/08/07 04:01:30 dlg Exp $  */
/* $EOM: pf_key_v2.c,v 1.79 2000/12/12 00:33:19 niklas Exp $     */

/*
 * Copyright (c) 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis.  All rights reserved.
 * Copyright (c) 2001 HÃ¥kan Olsson.  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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/*
 * This code was written under funding by Ericsson Radio Systems.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/uio.h>

#include <net/pfkeyv2.h>
#include <netinet/in.h>
#include <netinet/ip_ipsp.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <bitstring.h>

#include "cert.h"
#include "conf.h"
#include "connection.h"
#include "exchange.h"
#include "ipsec.h"
#include "ipsec_num.h"
#include "key.h"
#include "log.h"
#include "pf_key_v2.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"
#include "ui.h"
#include "util.h"

#include "policy.h"

#include "udp_encap.h"

#define IN6_IS_ADDR_FULL(a)                                             \
        ((*(u_int32_t *)(void *)(&(a)->s6_addr[0]) == 0xffffffff) &&    \
        (*(u_int32_t *)(void *)(&(a)->s6_addr[4]) == 0xffffffff) &&     \
        (*(u_int32_t *)(void *)(&(a)->s6_addr[8]) == 0xffffffff) &&     \
        (*(u_int32_t *)(void *)(&(a)->s6_addr[12]) == 0xffffffff))

#define ADDRESS_MAX sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"

/*
 * PF_KEY v2 always work with 64-bit entities and aligns on 64-bit boundaries.
 */
#define PF_KEY_V2_CHUNK 8
#define PF_KEY_V2_ROUND(x)                                              \
        (((x) + PF_KEY_V2_CHUNK - 1) & ~(PF_KEY_V2_CHUNK - 1))

/* How many microseconds we will wait for a reply from the PF_KEY socket.  */
#define PF_KEY_REPLY_TIMEOUT 1000

struct pf_key_v2_node {
        TAILQ_ENTRY(pf_key_v2_node) link;
        void           *seg;
        size_t          sz;
        int             cnt;
        u_int16_t       type;
        u_int8_t        flags;
};

TAILQ_HEAD(pf_key_v2_msg, pf_key_v2_node);

#define PF_KEY_V2_NODE_MALLOCED 1
#define PF_KEY_V2_NODE_MARK 2

/* Used to derive "unique" connection identifiers. */
int             connection_seq = 0;

static u_int8_t *pf_key_v2_convert_id(u_int8_t *, int, size_t *, int *);
static struct pf_key_v2_msg *pf_key_v2_call(struct pf_key_v2_msg *);
static struct pf_key_v2_node *pf_key_v2_find_ext(struct pf_key_v2_msg *,
                    u_int16_t);
static void     pf_key_v2_notify(struct pf_key_v2_msg *);
static struct pf_key_v2_msg *pf_key_v2_read(u_int32_t);
static u_int32_t pf_key_v2_seq(void);
static u_int32_t pf_key_v2_write(struct pf_key_v2_msg *);
static int      pf_key_v2_remove_conf(char *);
static int      pf_key_v2_conf_refhandle(int, char *);

static int      pf_key_v2_conf_refinc(int, char *);

/* The socket to use for PF_KEY interactions.  */
int      pf_key_v2_socket;

static struct pf_key_v2_msg *
pf_key_v2_msg_new(struct sadb_msg *msg, int flags)
{
        struct pf_key_v2_node *node;
        struct pf_key_v2_msg *ret;

        node = malloc(sizeof *node);
        if (!node)
                goto cleanup;
        ret = malloc(sizeof *ret);
        if (!ret)
                goto cleanup;
        TAILQ_INIT(ret);
        node->seg = msg;
        node->sz = sizeof *msg;
        node->type = 0;
        node->cnt = 1;
        node->flags = flags;
        TAILQ_INSERT_HEAD(ret, node, link);
        return ret;

cleanup:
        free(node);
        return 0;
}

/* Add a SZ sized segment SEG to the PF_KEY message MSG.  */
static int
pf_key_v2_msg_add(struct pf_key_v2_msg *msg, struct sadb_ext *ext, int flags)
{
        struct pf_key_v2_node *node;

        node = malloc(sizeof *node);
        if (!node)
                return -1;
        node->seg = ext;
        node->sz = ext->sadb_ext_len * PF_KEY_V2_CHUNK;
        node->type = ext->sadb_ext_type;
        node->flags = flags;
        TAILQ_FIRST(msg)->cnt++;
        TAILQ_INSERT_TAIL(msg, node, link);
        return 0;
}

/* Deallocate the PF_KEY message MSG.  */
static void
pf_key_v2_msg_free(struct pf_key_v2_msg *msg)
{
        struct pf_key_v2_node *np;

        np = TAILQ_FIRST(msg);
        while (np) {
                TAILQ_REMOVE(msg, np, link);
                if (np->flags & PF_KEY_V2_NODE_MALLOCED)
                        free(np->seg);
                free(np);
                np = TAILQ_FIRST(msg);
        }
        free(msg);
}

/* Just return a new sequence number.  */
static u_int32_t
pf_key_v2_seq(void)
{
        static u_int32_t seq = 0;

        return ++seq;
}

/*
 * Read a PF_KEY packet with SEQ as the sequence number, looping if necessary.
 * If SEQ is zero just read the first message we see, otherwise we queue
 * messages up until both the PID and the sequence number match.
 */
static struct pf_key_v2_msg *
pf_key_v2_read(u_int32_t seq)
{
        ssize_t         n;
        u_int8_t       *buf = 0;
        struct pf_key_v2_msg *ret = 0;
        struct sadb_msg *msg;
        struct sadb_msg hdr;
        struct sadb_ext *ext;
        struct timespec ts;
        struct pollfd   pfd[1];

        pfd[0].fd = pf_key_v2_socket;
        pfd[0].events = POLLIN;

        while (1) {
                /*
                 * If this is a read of a reply we should actually expect the
                 * reply to get lost as PF_KEY is an unreliable service per
                 * the specs. Currently we do this by setting a short timeout,
                 * and if it is not readable in that time, we fail the read.
                 */
                if (seq) {
                        n = poll(pfd, 1, PF_KEY_REPLY_TIMEOUT / 1000);
                        if (n == -1) {
                                log_error("pf_key_v2_read: poll() failed");
                                goto cleanup;
                        }
                        if (!n) {
                                log_print("pf_key_v2_read: "
                                    "no reply from PF_KEY");
                                goto cleanup;
                        }
                }
                n = recv(pf_key_v2_socket, &hdr, sizeof hdr, MSG_PEEK);
                if (n == -1) {
                        log_error("pf_key_v2_read: recv (%d, ...) failed",
                            pf_key_v2_socket);
                        goto cleanup;
                }
                if (n != sizeof hdr) {
                        log_error("pf_key_v2_read: recv (%d, ...) "
                            "returned short packet (%lu bytes)",
                            pf_key_v2_socket, (unsigned long) n);
                        goto cleanup;
                }
                buf = reallocarray(NULL, hdr.sadb_msg_len, PF_KEY_V2_CHUNK);
                if (!buf) {
                        log_error("pf_key_v2_read: reallocarray (%d, %d) failed",
                            hdr.sadb_msg_len, PF_KEY_V2_CHUNK);
                        goto cleanup;
                }
                n = hdr.sadb_msg_len * PF_KEY_V2_CHUNK;

                n = read(pf_key_v2_socket, buf, n);
                if (n == -1) {
                        log_error("pf_key_v2_read: read (%d, ...) failed",
                                  pf_key_v2_socket);
                        goto cleanup;
                }
                if (n != hdr.sadb_msg_len * PF_KEY_V2_CHUNK) {
                        log_print("pf_key_v2_read: read (%d, ...) "
                            "returned short packet (%lu bytes)",
                            pf_key_v2_socket, (unsigned long) n);
                        goto cleanup;
                }
                LOG_DBG_BUF((LOG_SYSDEP, 80, "pf_key_v2_read: msg", buf, n));

                /* We drop all messages that is not what we expect.  */
                msg = (struct sadb_msg *) buf;
                if (msg->sadb_msg_version != PF_KEY_V2 ||
                    (msg->sadb_msg_pid != 0 &&
                    msg->sadb_msg_pid != (u_int32_t) getpid())) {
                        if (seq) {
                                free(buf);
                                buf = 0;
                                continue;
                        } else {
                                LOG_DBG((LOG_SYSDEP, 90, "pf_key_v2_read:"
                                    "bad version (%d) or PID (%d, mine is "
                                    "%ld), ignored", msg->sadb_msg_version,
                                    msg->sadb_msg_pid, (long) getpid()));
                                goto cleanup;
                        }
                }
                /* Parse the message.  */
                ret = pf_key_v2_msg_new(msg, PF_KEY_V2_NODE_MALLOCED);
                if (!ret)
                        goto cleanup;
                buf = 0;
                for (ext = (struct sadb_ext *) (msg + 1);
                    (u_int8_t *) ext - (u_int8_t *) msg <
                    msg->sadb_msg_len * PF_KEY_V2_CHUNK;
                    ext = (struct sadb_ext *) ((u_int8_t *) ext +
                    ext->sadb_ext_len * PF_KEY_V2_CHUNK))
                        pf_key_v2_msg_add(ret, ext, 0);

                /*
                 * If the message is not the one we are waiting for, queue it
                 * up.
                 */
                if (seq && (msg->sadb_msg_pid != (u_int32_t) getpid() ||
                    msg->sadb_msg_seq != seq)) {
                        clock_gettime(CLOCK_MONOTONIC, &ts);
                        timer_add_event("pf_key_v2_notify",
                            (void (*) (void *)) pf_key_v2_notify, ret, &ts);
                        ret = 0;
                        continue;
                }
                return ret;
        }

cleanup:
        free(buf);
        if (ret)
                pf_key_v2_msg_free(ret);
        return 0;
}

/* Write the message in PMSG to the PF_KEY socket.  */
u_int32_t
pf_key_v2_write(struct pf_key_v2_msg *pmsg)
{
        struct iovec   *iov = 0;
        ssize_t         n;
        size_t          len;
        int             i, cnt = TAILQ_FIRST(pmsg)->cnt;
        char            header[80];
        struct sadb_msg *msg = TAILQ_FIRST(pmsg)->seg;
        struct pf_key_v2_node *np = TAILQ_FIRST(pmsg);

        iov = calloc(cnt, sizeof *iov);
        if (!iov) {
                log_error("pf_key_v2_write: malloc (%lu) failed",
                    cnt * (unsigned long) sizeof *iov);
                return 0;
        }
        msg->sadb_msg_version = PF_KEY_V2;
        msg->sadb_msg_errno = 0;
        msg->sadb_msg_reserved = 0;
        msg->sadb_msg_pid = getpid();
        if (!msg->sadb_msg_seq)
                msg->sadb_msg_seq = pf_key_v2_seq();

        /* Compute the iovec segments as well as the message length.  */
        len = 0;
        for (i = 0; i < cnt; i++) {
                iov[i].iov_base = np->seg;
                len += iov[i].iov_len = np->sz;

                /*
                 * XXX One can envision setting specific extension fields,
                 * like *_reserved ones here.  For now we require them to be
                 * set by the caller.
                 */

                np = TAILQ_NEXT(np, link);
        }
        msg->sadb_msg_len = len / PF_KEY_V2_CHUNK;

        for (i = 0; i < cnt; i++) {
                snprintf(header, sizeof header, "pf_key_v2_write: iov[%d]", i);
                LOG_DBG_BUF((LOG_SYSDEP, 80, header,
                    (u_int8_t *) iov[i].iov_base, iov[i].iov_len));
        }

        do {
                n = writev(pf_key_v2_socket, iov, cnt);
        } while (n == -1 && (errno == EAGAIN || errno == EINTR));
        if (n == -1) {
                log_error("pf_key_v2_write: writev (%d, %p, %d) failed",
                    pf_key_v2_socket, iov, cnt);
                goto cleanup;
        }
        if ((size_t) n != len) {
                log_error("pf_key_v2_write: "
                    "writev (%d, ...) returned prematurely (%lu)",
                    pf_key_v2_socket, (unsigned long) n);
                goto cleanup;
        }
        free(iov);
        return msg->sadb_msg_seq;

cleanup:
        free(iov);
        return 0;
}

/*
 * Do a PF_KEY "call", i.e. write a message MSG, read the reply and return
 * it to the caller.
 */
static struct pf_key_v2_msg *
pf_key_v2_call(struct pf_key_v2_msg *msg)
{
        u_int32_t       seq;

        seq = pf_key_v2_write(msg);
        if (!seq)
                return 0;
        return pf_key_v2_read(seq);
}

/* Find the TYPE extension in MSG.  Return zero if none found.  */
static struct pf_key_v2_node *
pf_key_v2_find_ext(struct pf_key_v2_msg *msg, u_int16_t type)
{
        struct pf_key_v2_node *ext;

        for (ext = TAILQ_NEXT(TAILQ_FIRST(msg), link); ext;
            ext = TAILQ_NEXT(ext, link))
                if (ext->type == type)
                        return ext;
        return 0;
}

/*
 * Open the PF_KEYv2 sockets and return the descriptor used for notifies.
 * Return -1 for failure and -2 if no notifies will show up.
 */
int
pf_key_v2_open(void)
{
        int             fd = -1, err;
        struct sadb_msg msg;
        struct pf_key_v2_msg *regmsg = 0, *ret = 0;

        /* Open the socket we use to speak to IPsec. */
        pf_key_v2_socket = -1;
        fd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
        if (fd == -1) {
                log_error("pf_key_v2_open: "
                    "socket (PF_KEY, SOCK_RAW, PF_KEY_V2) failed");
                goto cleanup;
        }
        pf_key_v2_socket = fd;

        /* Register it to get ESP and AH acquires from the kernel.  */
        msg.sadb_msg_seq = 0;
        msg.sadb_msg_type = SADB_REGISTER;
        msg.sadb_msg_satype = SADB_SATYPE_ESP;
        regmsg = pf_key_v2_msg_new(&msg, 0);
        if (!regmsg)
                goto cleanup;
        ret = pf_key_v2_call(regmsg);
        pf_key_v2_msg_free(regmsg);
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
                goto cleanup;
        }
        /* XXX Register the accepted transforms.  */

        pf_key_v2_msg_free(ret);
        ret = 0;

        msg.sadb_msg_seq = 0;
        msg.sadb_msg_type = SADB_REGISTER;
        msg.sadb_msg_satype = SADB_SATYPE_AH;
        regmsg = pf_key_v2_msg_new(&msg, 0);
        if (!regmsg)
                goto cleanup;
        ret = pf_key_v2_call(regmsg);
        pf_key_v2_msg_free(regmsg);
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
                goto cleanup;
        }
        /* XXX Register the accepted transforms.  */

        pf_key_v2_msg_free(ret);
        ret = 0;

        msg.sadb_msg_seq = 0;
        msg.sadb_msg_type = SADB_REGISTER;
        msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
        regmsg = pf_key_v2_msg_new(&msg, 0);
        if (!regmsg)
                goto cleanup;
        ret = pf_key_v2_call(regmsg);
        pf_key_v2_msg_free(regmsg);
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_open: REGISTER: %s", strerror(err));
                goto cleanup;
        }
        /* XXX Register the accepted transforms.  */

        pf_key_v2_msg_free(ret);

        return fd;

cleanup:
        if (pf_key_v2_socket != -1) {
                close(pf_key_v2_socket);
                pf_key_v2_socket = -1;
        }
        if (ret)
                pf_key_v2_msg_free(ret);
        return -1;
}

/*
 * Generate a SPI for protocol PROTO and the source/destination pair given by
 * SRC, SRCLEN, DST & DSTLEN.  Stash the SPI size in SZ.
 */
u_int8_t *
pf_key_v2_get_spi(size_t *sz, u_int8_t proto, struct sockaddr *src,
    struct sockaddr *dst, u_int32_t seq)
{
        struct sadb_msg msg;
        struct sadb_sa *sa;
        struct sadb_address *addr = 0;
        struct sadb_spirange spirange;
        struct pf_key_v2_msg *getspi = 0, *ret = 0;
        struct pf_key_v2_node *ext;
        u_int8_t       *spi = 0;
        int             len, err;

        msg.sadb_msg_type = SADB_GETSPI;
        switch (proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_get_spi: invalid proto %d", proto);
                goto cleanup;
        }

        /* Set the sequence number from the ACQUIRE message. */
        msg.sadb_msg_seq = seq;
        getspi = pf_key_v2_msg_new(&msg, 0);
        if (!getspi)
                goto cleanup;

        /* Setup the ADDRESS extensions.  */
        len =
            sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(src));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, src, SA_LEN(src));
        switch (((struct sockaddr *) (addr + 1))->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(getspi, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        len = sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(dst));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, dst, SA_LEN(dst));
        switch (((struct sockaddr *) (addr + 1))->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(getspi, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        /* Setup the SPIRANGE extension.  */
        spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE;
        spirange.sadb_spirange_len = sizeof spirange / PF_KEY_V2_CHUNK;
        if (proto == IPSEC_PROTO_IPCOMP) {
                spirange.sadb_spirange_min = CPI_RESERVED_MAX + 1;
                spirange.sadb_spirange_max = CPI_PRIVATE_MIN - 1;
        } else {
                spirange.sadb_spirange_min = IPSEC_SPI_LOW;
                spirange.sadb_spirange_max = 0xffffffff;
        }
        spirange.sadb_spirange_reserved = 0;
        if (pf_key_v2_msg_add(getspi, (struct sadb_ext *)&spirange, 0) == -1)
                goto cleanup;

        ret = pf_key_v2_call(getspi);
        pf_key_v2_msg_free(getspi);
        getspi = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_get_spi: GETSPI: %s", strerror(err));
                goto cleanup;
        }
        ext = pf_key_v2_find_ext(ret, SADB_EXT_SA);
        if (!ext) {
                log_print("pf_key_v2_get_spi: no SA extension found");
                goto cleanup;
        }
        sa = ext->seg;

        /* IPCOMP CPIs are only 16 bits long.  */
        *sz = (proto == IPSEC_PROTO_IPCOMP) ? sizeof(u_int16_t)
                : sizeof sa->sadb_sa_spi;
        spi = malloc(*sz);
        if (!spi)
                goto cleanup;
        /* XXX This is ugly.  */
        if (proto == IPSEC_PROTO_IPCOMP) {
                u_int32_t       tspi = ntohl(sa->sadb_sa_spi);
                *(u_int16_t *) spi = htons((u_int16_t) tspi);
        } else
                memcpy(spi, &sa->sadb_sa_spi, *sz);

        pf_key_v2_msg_free(ret);

        LOG_DBG_BUF((LOG_SYSDEP, 50, "pf_key_v2_get_spi: spi", spi, *sz));
        return spi;

cleanup:
        free(spi);
        free(addr);
        if (getspi)
                pf_key_v2_msg_free(getspi);
        if (ret)
                pf_key_v2_msg_free(ret);
        return 0;
}

/* Fetch SA information from the kernel. XXX OpenBSD only?  */
struct sa_kinfo *
pf_key_v2_get_kernel_sa(u_int8_t *spi, size_t spi_sz, u_int8_t proto,
    struct sockaddr *dst)
{
        struct sadb_msg msg;
        struct sadb_sa *ssa;
        struct sadb_address *addr = 0;
        struct sockaddr *sa;
        struct sadb_lifetime *life;
        struct pf_key_v2_msg *gettdb = 0, *ret = 0;
        struct pf_key_v2_node *ext;
        static struct sa_kinfo ksa;
        struct sadb_x_udpencap *udpencap;
        int len, err;

        if (spi_sz != sizeof (ssa->sadb_sa_spi))
                return 0;

        msg.sadb_msg_type = SADB_GET;
        switch (proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_get_kernel_sa: invalid proto %d", proto);
                goto cleanup;
        }

        gettdb = pf_key_v2_msg_new(&msg, 0);
        if (!gettdb)
                goto cleanup;

        /* SPI */
        ssa = calloc(1, sizeof *ssa);
        if (!ssa) {
                log_print("pf_key_v2_get_kernel_sa: calloc(1, %lu) failed",
                    (unsigned long)sizeof *ssa);
                goto cleanup;
        }

        ssa->sadb_sa_exttype = SADB_EXT_SA;
        ssa->sadb_sa_len = sizeof *ssa / PF_KEY_V2_CHUNK;
        memcpy(&ssa->sadb_sa_spi, spi, sizeof ssa->sadb_sa_spi);
        ssa->sadb_sa_state = SADB_SASTATE_MATURE;
        if (pf_key_v2_msg_add(gettdb, (struct sadb_ext *)ssa,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        ssa = 0;

        /* Address */
        len =
            sizeof(struct sadb_address) + PF_KEY_V2_ROUND(SA_LEN(dst));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, dst, SA_LEN(dst));
        switch (((struct sockaddr *) (addr + 1))->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(gettdb, (struct sadb_ext *)addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        ret = pf_key_v2_call(gettdb);
        pf_key_v2_msg_free(gettdb);
        gettdb = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_get_kernel_sa: SADB_GET: %s",
                    strerror(err));
                goto cleanup;
        }

        /* Extract the data.  */
        bzero(&ksa, sizeof ksa);

        ext = pf_key_v2_find_ext(ret, SADB_EXT_SA);
        if (!ext)
                goto cleanup;

        ssa = (struct sadb_sa *)ext;
        ksa.spi = ssa->sadb_sa_spi;
        ksa.wnd = ssa->sadb_sa_replay;
        ksa.flags = ssa->sadb_sa_flags;

        ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_CURRENT);
        if (ext) {
                life = (struct sadb_lifetime *)ext->seg;
                ksa.cur_allocations = life->sadb_lifetime_allocations;
                ksa.cur_bytes = life->sadb_lifetime_bytes;
                ksa.first_use = life->sadb_lifetime_usetime;
                ksa.established = life->sadb_lifetime_addtime;
        }

        ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_SOFT);
        if (ext) {
                life = (struct sadb_lifetime *)ext->seg;
                ksa.soft_allocations = life->sadb_lifetime_allocations;
                ksa.soft_bytes = life->sadb_lifetime_bytes;
                ksa.soft_timeout = life->sadb_lifetime_addtime;
                ksa.soft_first_use = life->sadb_lifetime_usetime;
        }

        ext = pf_key_v2_find_ext(ret, SADB_EXT_LIFETIME_HARD);
        if (ext) {
                life = (struct sadb_lifetime *)ext->seg;
                ksa.exp_allocations = life->sadb_lifetime_allocations;
                ksa.exp_bytes = life->sadb_lifetime_bytes;
                ksa.exp_timeout = life->sadb_lifetime_addtime;
                ksa.exp_first_use = life->sadb_lifetime_usetime;
        }

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_LIFETIME_LASTUSE);
        if (ext) {
                life = (struct sadb_lifetime *)ext->seg;
                ksa.last_used = life->sadb_lifetime_usetime;
        }

        ext = pf_key_v2_find_ext(ret, SADB_EXT_ADDRESS_SRC);
        if (ext) {
                sa = (struct sockaddr *)ext->seg;
                memcpy(&ksa.src, sa,
                    sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) :
                    sizeof(struct sockaddr_in6));
        }

        ext = pf_key_v2_find_ext(ret, SADB_EXT_ADDRESS_DST);
        if (ext) {
                sa = (struct sockaddr *)ext->seg;
                memcpy(&ksa.dst, sa,
                    sa->sa_family == AF_INET ? sizeof(struct sockaddr_in) :
                    sizeof(struct sockaddr_in6));
        }

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_UDPENCAP);
        if (ext) {
                udpencap = (struct sadb_x_udpencap *)ext->seg;
                ksa.udpencap_port = udpencap->sadb_x_udpencap_port;
        }

        pf_key_v2_msg_free(ret);

        LOG_DBG_BUF((LOG_SYSDEP, 50, "pf_key_v2_get_kernel_sa: spi", spi,
            spi_sz));

        return &ksa;

  cleanup:
        free(addr);
        if (gettdb)
                pf_key_v2_msg_free(gettdb);
        if (ret)
                pf_key_v2_msg_free(ret);
        return 0;
}

static void
pf_key_v2_setup_sockaddr(void *res, struct sockaddr *src,
    struct sockaddr *dst, in_port_t port, int ingress)
{
        struct sockaddr_in *ip4_sa;
        struct sockaddr_in6 *ip6_sa;
        u_int8_t       *p;

        switch (src->sa_family) {
        case AF_INET:
                ip4_sa = (struct sockaddr_in *) res;
                ip4_sa->sin_family = AF_INET;
                ip4_sa->sin_len = sizeof *ip4_sa;
                ip4_sa->sin_port = port;
                if (dst)
                        p = (u_int8_t *) (ingress ?
                            &((struct sockaddr_in *)src)->sin_addr.s_addr :
                            &((struct sockaddr_in *)dst)->sin_addr.s_addr);
                else
                        p = (u_int8_t *)&((struct sockaddr_in *)src)->sin_addr.s_addr;
                ip4_sa->sin_addr.s_addr = *((in_addr_t *) p);
                break;

        case AF_INET6:
                ip6_sa = (struct sockaddr_in6 *) res;
                ip6_sa->sin6_family = AF_INET6;
                ip6_sa->sin6_len = sizeof *ip6_sa;
                ip6_sa->sin6_port = port;
                if (dst)
                        p = (u_int8_t *) (ingress ?
                            &((struct sockaddr_in6 *)src)->sin6_addr.s6_addr :
                            &((struct sockaddr_in6 *)dst)->sin6_addr.s6_addr);
                else
                        p = (u_int8_t *)&((struct sockaddr_in6 *)src)->sin6_addr.s6_addr;
                memcpy(ip6_sa->sin6_addr.s6_addr, p, sizeof(struct in6_addr));
                break;

        default:
                log_print("pf_key_v2_setup_sockaddr: unknown family %d\n",
                    src->sa_family);
                break;
        }
}

/*
 * Store/update a PF_KEY_V2 security association with full information from the
 * IKE SA and PROTO into the kernel.  INCOMING is set if we are setting the
 * parameters for the incoming SA, and cleared otherwise.
 */
int
pf_key_v2_set_spi(struct sa *sa, struct proto *proto, int incoming,
    struct sa *isakmp_sa)
{
        struct sadb_msg msg;
        struct sadb_sa  ssa;
        struct sadb_x_tag *stag = NULL;
        struct sadb_lifetime *life = 0;
        struct sadb_address *addr = 0;
        struct sadb_key *key = 0;
        struct sadb_ident *sid = 0;
        struct sockaddr *src, *dst;
        struct pf_key_v2_msg *update = 0, *ret = 0;
        struct ipsec_proto *iproto = proto->data;
        size_t          len;
        int             keylen, hashlen, err;
        u_int8_t       *pp;
        int             idtype;
        struct ipsec_sa *isa = sa->data;
        struct sadb_protocol flowtype, tprotocol;
        struct sadb_x_udpencap udpencap;
        char           *addr_str, *s;
        char            iface_str[32];

        msg.sadb_msg_type = incoming ? SADB_UPDATE : SADB_ADD;
        switch (proto->proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                keylen = ipsec_esp_enckeylength(proto);
                hashlen = ipsec_esp_authkeylength(proto);

                switch (proto->id) {
                case IPSEC_ESP_3DES:
                        ssa.sadb_sa_encrypt = SADB_EALG_3DESCBC;
                        break;

                case IPSEC_ESP_AES:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_AES;
                        break;

                case IPSEC_ESP_AES_CTR:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_AESCTR;
                        break;

                case IPSEC_ESP_AES_GCM_16:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_AESGCM16;
                        break;

                case IPSEC_ESP_AES_GMAC:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_AESGMAC;
                        break;

                case IPSEC_ESP_CAST:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_CAST;
                        break;

                case IPSEC_ESP_BLOWFISH:
                        ssa.sadb_sa_encrypt = SADB_X_EALG_BLF;
                        break;

                case IPSEC_ESP_NULL:
                        ssa.sadb_sa_encrypt = SADB_EALG_NULL;
                        break;

                default:
                        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
                            "unknown encryption algorithm %d", proto->id));
                        return -1;
                }

                switch (iproto->auth) {
                case IPSEC_AUTH_HMAC_MD5:
                        ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
                        break;

                case IPSEC_AUTH_HMAC_SHA:
                        ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
                        break;

                case IPSEC_AUTH_HMAC_RIPEMD:
                        ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC;
                        break;

                case IPSEC_AUTH_HMAC_SHA2_256:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_256;
                        break;

                case IPSEC_AUTH_HMAC_SHA2_384:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_384;
                        break;

                case IPSEC_AUTH_HMAC_SHA2_512:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_512;
                        break;

                case IPSEC_AUTH_DES_MAC:
                case IPSEC_AUTH_KPDK:
                        /* XXX We should be supporting KPDK */
                        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
                            "unknown authentication algorithm %d",
                            iproto->auth));
                        return -1;

                default:
                        ssa.sadb_sa_auth = SADB_AALG_NONE;
                }
                break;

        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                hashlen = ipsec_ah_keylength(proto);
                keylen = 0;

                ssa.sadb_sa_encrypt = SADB_EALG_NONE;
                switch (proto->id) {
                case IPSEC_AH_MD5:
                        ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
                        break;

                case IPSEC_AH_SHA:
                        ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
                        break;

                case IPSEC_AH_RIPEMD:
                        ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC;
                        break;

                case IPSEC_AH_SHA2_256:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_256;
                        break;

                case IPSEC_AH_SHA2_384:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_384;
                        break;

                case IPSEC_AH_SHA2_512:
                        ssa.sadb_sa_auth = SADB_X_AALG_SHA2_512;
                        break;

                default:
                        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: "
                            "unknown authentication algorithm %d", proto->id));
                        goto cleanup;
                }
                break;

        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                ssa.sadb_sa_auth = SADB_AALG_NONE;
                keylen = 0;
                hashlen = 0;

                /*
                 * Put compression algorithm type in the sadb_sa_encrypt
                 * field.
                 */
                switch (proto->id) {
                case IPSEC_IPCOMP_OUI:
                        ssa.sadb_sa_encrypt = SADB_X_CALG_OUI;
                        break;

                case IPSEC_IPCOMP_DEFLATE:
                        ssa.sadb_sa_encrypt = SADB_X_CALG_DEFLATE;
                        break;

                default:
                        break;
                }
                break;

        default:
                log_print("pf_key_v2_set_spi: invalid proto %d", proto->proto);
                goto cleanup;
        }
        if (incoming)
                sa->transport->vtbl->get_src(sa->transport, &dst);
        else
                sa->transport->vtbl->get_dst(sa->transport, &dst);
        msg.sadb_msg_seq = sa->seq;
        update = pf_key_v2_msg_new(&msg, 0);
        if (!update)
                goto cleanup;

        /* Setup the rest of the SA extension.  */
        ssa.sadb_sa_exttype = SADB_EXT_SA;
        ssa.sadb_sa_len = sizeof ssa / PF_KEY_V2_CHUNK;
        if (proto->spi_sz[incoming] == 2)       /* IPCOMP uses 16bit CPIs.  */
                ssa.sadb_sa_spi = htonl(proto->spi[incoming][0] << 8 |
                    proto->spi[incoming][1]);
        else
                memcpy(&ssa.sadb_sa_spi, proto->spi[incoming],
                    sizeof ssa.sadb_sa_spi);
        ssa.sadb_sa_replay = conf_get_str("General", "Shared-SADB") ? 0 :
            iproto->replay_window;
        ssa.sadb_sa_state = SADB_SASTATE_MATURE;
        ssa.sadb_sa_flags = 0;
        if (iproto->encap_mode == IPSEC_ENCAP_TUNNEL ||
            iproto->encap_mode == IPSEC_ENCAP_UDP_ENCAP_TUNNEL ||
            iproto->encap_mode == IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT)
                ssa.sadb_sa_flags = SADB_X_SAFLAGS_TUNNEL;

        if (isakmp_sa->flags & SA_FLAG_NAT_T_ENABLE) {
                bzero(&udpencap, sizeof udpencap);
                ssa.sadb_sa_flags |= SADB_X_SAFLAGS_UDPENCAP;
                udpencap.sadb_x_udpencap_exttype = SADB_X_EXT_UDPENCAP;
                udpencap.sadb_x_udpencap_len =
                    sizeof udpencap / PF_KEY_V2_CHUNK;
                udpencap.sadb_x_udpencap_port = sockaddr_port(dst);
                if (pf_key_v2_msg_add(update, (struct sadb_ext *)&udpencap, 0)
                    == -1)
                        goto cleanup;
        }

        if (pf_key_v2_msg_add(update, (struct sadb_ext *)&ssa, 0) == -1)
                goto cleanup;

        if (sa->seconds || sa->kilobytes) {
                /* Setup the hard limits.  */
                life = malloc(sizeof *life);
                if (!life)
                        goto cleanup;
                life->sadb_lifetime_len = sizeof *life / PF_KEY_V2_CHUNK;
                life->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
                life->sadb_lifetime_allocations = 0;
                life->sadb_lifetime_bytes = sa->kilobytes * 1024;
                /*
                 * XXX I am not sure which one is best in security respect.
                 * Maybe the RFCs actually mandate what a lifetime really is.
                 */
#if 0
                life->sadb_lifetime_addtime = 0;
                life->sadb_lifetime_usetime = sa->seconds;
#else
                life->sadb_lifetime_addtime = sa->seconds;
                life->sadb_lifetime_usetime = 0;
#endif
                if (pf_key_v2_msg_add(update, (struct sadb_ext *) life,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
                life = 0;

                /*
                 * Setup the soft limits, we use 90 % of the hard ones.
                 * XXX A configurable ratio would be better.
                 */
                life = malloc(sizeof *life);
                if (!life)
                        goto cleanup;
                life->sadb_lifetime_len = sizeof *life / PF_KEY_V2_CHUNK;
                life->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
                life->sadb_lifetime_allocations = 0;
                life->sadb_lifetime_bytes = sa->kilobytes * 1024 * 9 / 10;
                /*
                 * XXX I am not sure which one is best in security respect.
                 * Maybe the RFCs actually mandate what a lifetime really is.
                 */
#if 0
                life->sadb_lifetime_addtime = 0;
                life->sadb_lifetime_usetime = sa->seconds * 9 / 10;
#else
                life->sadb_lifetime_addtime = sa->seconds * 9 / 10;
                life->sadb_lifetime_usetime = 0;
#endif
                if (pf_key_v2_msg_add(update, (struct sadb_ext *) life,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
                life = 0;
        }
        /*
         * Setup the ADDRESS extensions.
         */
        if (incoming)
                sa->transport->vtbl->get_dst(sa->transport, &src);
        else
                sa->transport->vtbl->get_src(sa->transport, &src);
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(src));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, src, SA_LEN(src));
        switch (((struct sockaddr *) (addr + 1))->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(dst));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, dst, SA_LEN(dst));
        switch (((struct sockaddr *) (addr + 1))->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        if (proto->proto != IPSEC_PROTO_IPCOMP) {
                /* Setup the KEY extensions.  */
                if (hashlen) {
                        len = sizeof *key + PF_KEY_V2_ROUND(hashlen);
                        key = malloc(len);
                        if (!key)
                                goto cleanup;
                        key->sadb_key_exttype = SADB_EXT_KEY_AUTH;
                        key->sadb_key_len = len / PF_KEY_V2_CHUNK;
                        key->sadb_key_bits = hashlen * 8;
                        key->sadb_key_reserved = 0;
                        memcpy(key + 1,
                            iproto->keymat[incoming] +
                            (proto->proto ==
                                IPSEC_PROTO_IPSEC_ESP ? keylen : 0),
                            hashlen);
                        if (pf_key_v2_msg_add(update, (struct sadb_ext *) key,
                            PF_KEY_V2_NODE_MALLOCED) == -1)
                                goto cleanup;
                        key = 0;
                }
                if (keylen) {
                        len = sizeof *key + PF_KEY_V2_ROUND(keylen);
                        key = malloc(len);
                        if (!key)
                                goto cleanup;
                        key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
                        key->sadb_key_len = len / PF_KEY_V2_CHUNK;
                        key->sadb_key_bits = keylen * 8;
                        key->sadb_key_reserved = 0;
                        memcpy(key + 1, iproto->keymat[incoming], keylen);
                        if (pf_key_v2_msg_add(update, (struct sadb_ext *) key,
                            PF_KEY_V2_NODE_MALLOCED) == -1)
                                goto cleanup;
                        key = 0;
                }
        }
        /* Setup identity extensions. */
        if (isakmp_sa->id_i) {
                pp = pf_key_v2_convert_id(isakmp_sa->id_i, isakmp_sa->id_i_len,
                    &len, &idtype);
                if (!pp)
                        goto nosid;

                sid = calloc(PF_KEY_V2_ROUND(len + 1) + sizeof *sid,
                    sizeof(u_int8_t));
                if (!sid) {
                        free(pp);
                        goto cleanup;
                }
                sid->sadb_ident_type = idtype;
                sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK) +
                    PF_KEY_V2_ROUND(len + 1) / PF_KEY_V2_CHUNK;
                if ((isakmp_sa->initiator && !incoming) ||
                    (!isakmp_sa->initiator && incoming))
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;
                else
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;

                memcpy(sid + 1, pp, len);
                free(pp);

                if (pf_key_v2_msg_add(update, (struct sadb_ext *) sid,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
                sid = 0;

nosid:
                free(sid);
                sid = 0;
        }
        if (isakmp_sa->id_r) {
                pp = pf_key_v2_convert_id(isakmp_sa->id_r, isakmp_sa->id_r_len,
                    &len, &idtype);
                if (!pp)
                        goto nodid;

                sid = calloc(PF_KEY_V2_ROUND(len + 1) + sizeof *sid,
                    sizeof(u_int8_t));
                if (!sid) {
                        free(pp);
                        goto cleanup;
                }
                sid->sadb_ident_type = idtype;
                sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK) +
                    PF_KEY_V2_ROUND(len + 1) / PF_KEY_V2_CHUNK;
                if ((isakmp_sa->initiator && !incoming) ||
                    (!isakmp_sa->initiator && incoming))
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;
                else
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;

                memcpy(sid + 1, pp, len);
                free(pp);

                if (pf_key_v2_msg_add(update, (struct sadb_ext *) sid,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
                sid = 0;

nodid:
                free(sid);
                sid = 0;
        }

        /* Setup the flow type extension.  */
        bzero(&flowtype, sizeof flowtype);
        flowtype.sadb_protocol_exttype = SADB_X_EXT_FLOW_TYPE;
        flowtype.sadb_protocol_len = sizeof flowtype / PF_KEY_V2_CHUNK;
        flowtype.sadb_protocol_direction = incoming ?
            IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;

        if (pf_key_v2_msg_add(update, (struct sadb_ext *)&flowtype, 0) == -1)
                goto cleanup;

        bzero(&tprotocol, sizeof tprotocol);
        tprotocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL;
        tprotocol.sadb_protocol_len = sizeof tprotocol / PF_KEY_V2_CHUNK;
        tprotocol.sadb_protocol_proto = isa->tproto;

        if (pf_key_v2_msg_add(update, (struct sadb_ext *)&tprotocol,
            0) == -1)
                goto cleanup;

        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(isa->src_net));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = incoming ?
            SADB_X_EXT_DST_FLOW : SADB_X_EXT_SRC_FLOW;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, isa->src_net, 0, isa->sport, 0);
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype =
                incoming ? SADB_X_EXT_DST_MASK : SADB_X_EXT_SRC_MASK;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, isa->src_mask, 0,
            isa->sport ? 0xffff : 0, 0);
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = incoming ?
            SADB_X_EXT_SRC_FLOW : SADB_X_EXT_DST_FLOW;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, isa->dst_net, 0, isa->dport, 0);
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype =
                incoming ? SADB_X_EXT_SRC_MASK : SADB_X_EXT_DST_MASK;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, isa->dst_mask, 0,
            isa->dport ? 0xffff : 0, 0);
        if (pf_key_v2_msg_add(update, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        /* Add a pf tag to matching packets of this SA. */
        if (sa->tag != NULL) {
                len = sizeof(*stag) + PF_KEY_V2_ROUND(strlen(sa->tag) + 1);
                if ((stag = calloc(1, len)) == NULL)
                        goto cleanup;
                stag->sadb_x_tag_exttype = SADB_X_EXT_TAG;
                stag->sadb_x_tag_len = len / PF_KEY_V2_CHUNK;
                stag->sadb_x_tag_taglen = strlen(sa->tag) + 1;
                s = (char *)(stag + 1);
                strlcpy(s, sa->tag, stag->sadb_x_tag_taglen);
                if (pf_key_v2_msg_add(update, (struct sadb_ext *)stag,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
        }

        if (sa->flags & SA_FLAG_IFACE) {
                struct sadb_x_iface *siface;

                len = sizeof(*siface);
                siface = calloc(1, len);
                if (siface == NULL)
                        goto cleanup;

                siface->sadb_x_iface_len = len / PF_KEY_V2_CHUNK;
                siface->sadb_x_iface_exttype = SADB_X_EXT_IFACE;
                siface->sadb_x_iface_unit = sa->iface;
                siface->sadb_x_iface_direction = incoming ?
                    IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;

                if (pf_key_v2_msg_add(update, (struct sadb_ext *)siface,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;

                snprintf(iface_str, sizeof(iface_str), "iface %u", sa->iface);
        }

        /* XXX Here can sensitivity extensions be setup.  */

        if (sockaddr2text(dst, &addr_str, 0))
                addr_str = 0;

        LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_set_spi: "
            "satype %d dst %s SPI 0x%x%s%s%s", msg.sadb_msg_satype,
            addr_str ? addr_str : "unknown",
            ntohl(ssa.sadb_sa_spi), sa->tag ? " tag " : "",
            sa->tag ? sa->tag : "", iface_str));

        free(addr_str);

        /*
         * Although PF_KEY knows about expirations, it is unreliable per the
         * specs thus we need to do them inside isakmpd as well.
         */
        if (sa->seconds)
                if (sa_setup_expirations(sa))
                        goto cleanup;

        ret = pf_key_v2_call(update);
        pf_key_v2_msg_free(update);
        update = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        pf_key_v2_msg_free(ret);
        ret = 0;

        /*
         * If we are doing an addition into an SADB shared with our peer,
         * errors here are to be expected as the peer will already have
         * created the SA, and can thus be ignored.
         */
        if (err && !(msg.sadb_msg_type == SADB_ADD &&
            conf_get_str("General", "Shared-SADB"))) {
                log_print("pf_key_v2_set_spi: %s: %s",
                    msg.sadb_msg_type == SADB_ADD ? "ADD" : "UPDATE",
                    strerror(err));
                goto cleanup;
        }
        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_set_spi: done"));

        return 0;

cleanup:
        free(sid);
        free(addr);
        free(life);
        free(key);
        if (update)
                pf_key_v2_msg_free(update);
        if (ret)
                pf_key_v2_msg_free(ret);
        return -1;
}

static __inline__ int
pf_key_v2_mask_to_bits(u_int32_t mask)
{
        u_int32_t       hmask = ntohl(mask);

        return (33 - ffs(~hmask + 1)) % 33;
}

static int
pf_key_v2_mask6_to_bits(u_int8_t *mask)
{
        int             n;

        bit_ffc(mask, 128, &n);
        return n == -1 ? 128 : n;
}

/*
 * Enable/disable a flow.
 * XXX Assumes OpenBSD {ADD,DEL}FLOW extensions.
 */
static int
pf_key_v2_flow(struct sockaddr *laddr, struct sockaddr *lmask,
    struct sockaddr *raddr, struct sockaddr *rmask,
    u_int8_t tproto, u_int16_t sport, u_int16_t dport,
    u_int8_t *spi, u_int8_t proto, struct sockaddr *dst,
    struct sockaddr *src, int delete, int ingress,
    u_int8_t srcid_type, u_int8_t *srcid, int srcid_len,
    u_int8_t dstid_type, u_int8_t *dstid, int dstid_len,
    struct ipsec_proto *iproto)
{
        char           *laddr_str, *lmask_str, *raddr_str, *rmask_str;

        struct sadb_msg msg;
        struct sadb_protocol flowtype;
        struct sadb_ident *sid = 0;
        struct sadb_address *addr = 0;
        struct sadb_protocol tprotocol;
        struct pf_key_v2_msg *flow = 0, *ret = 0;
        size_t          len;
        int             err;

        msg.sadb_msg_type = delete ? SADB_X_DELFLOW : SADB_X_ADDFLOW;
        switch (proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_flow: invalid proto %d", proto);
                goto cleanup;
        }
        msg.sadb_msg_seq = 0;
        flow = pf_key_v2_msg_new(&msg, 0);
        if (!flow)
                goto cleanup;

        if (!delete) {
                /* Setup the source ID, if provided. */
                if (srcid) {
                        sid = calloc(
                            PF_KEY_V2_ROUND(srcid_len + 1) + sizeof *sid,
                            sizeof(u_int8_t));
                        if (!sid)
                                goto cleanup;

                        sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK)
                            + PF_KEY_V2_ROUND(srcid_len + 1) / PF_KEY_V2_CHUNK;
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_SRC;
                        sid->sadb_ident_type = srcid_type;

                        memcpy(sid + 1, srcid, srcid_len);

                        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) sid,
                            PF_KEY_V2_NODE_MALLOCED) == -1)
                                goto cleanup;

                        sid = 0;
                }
                /* Setup the destination ID, if provided. */
                if (dstid) {
                        sid = calloc(
                            PF_KEY_V2_ROUND(dstid_len + 1) + sizeof *sid,
                            sizeof(u_int8_t));
                        if (!sid)
                                goto cleanup;

                        sid->sadb_ident_len = ((sizeof *sid) / PF_KEY_V2_CHUNK)
                            + PF_KEY_V2_ROUND(dstid_len + 1) / PF_KEY_V2_CHUNK;
                        sid->sadb_ident_exttype = SADB_EXT_IDENTITY_DST;
                        sid->sadb_ident_type = dstid_type;

                        memcpy(sid + 1, dstid, dstid_len);

                        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) sid,
                            PF_KEY_V2_NODE_MALLOCED) == -1)
                                goto cleanup;

                        sid = 0;
                }
        }
        /* Setup the flow type extension.  */
        bzero(&flowtype, sizeof flowtype);
        flowtype.sadb_protocol_exttype = SADB_X_EXT_FLOW_TYPE;
        flowtype.sadb_protocol_len = sizeof flowtype / PF_KEY_V2_CHUNK;
        flowtype.sadb_protocol_direction =
            ingress ? IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;
        flowtype.sadb_protocol_proto = SADB_X_FLOW_TYPE_REQUIRE;

        if (pf_key_v2_msg_add(flow, (struct sadb_ext *)&flowtype, 0) == -1)
                goto cleanup;

        /*
         * Setup the ADDRESS extensions.
         */
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(src));
        if (!delete)
        {
                addr = calloc(1, len);
                if (!addr)
                        goto cleanup;
                addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
                addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
                addr->sadb_address_reserved = 0;
                pf_key_v2_setup_sockaddr(addr + 1, src, dst, 0, ingress);
                if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
                    PF_KEY_V2_NODE_MALLOCED) == -1)
                        goto cleanup;
                addr = 0;
        }
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(laddr));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_X_EXT_SRC_FLOW;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, laddr, 0, sport, 0);
        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_X_EXT_SRC_MASK;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, lmask, 0, sport ? 0xffff : 0, 0);
        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_X_EXT_DST_FLOW;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, raddr, 0, dport, 0);
        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_X_EXT_DST_MASK;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        pf_key_v2_setup_sockaddr(addr + 1, rmask, 0, dport ? 0xffff : 0, 0);
        if (pf_key_v2_msg_add(flow, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        /* Setup the protocol extension.  */
        bzero(&tprotocol, sizeof tprotocol);
        tprotocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL;
        tprotocol.sadb_protocol_len = sizeof tprotocol / PF_KEY_V2_CHUNK;
        tprotocol.sadb_protocol_proto = tproto;

        if (pf_key_v2_msg_add(flow, (struct sadb_ext *)&tprotocol, 0) == -1)
                goto cleanup;

        if (sockaddr2text(laddr, &laddr_str, 0))
                laddr_str = 0;
        if (sockaddr2text(lmask, &lmask_str, 0))
                lmask_str = 0;
        if (sockaddr2text(raddr, &raddr_str, 0))
                raddr_str = 0;
        if (sockaddr2text(rmask, &rmask_str, 0))
                rmask_str = 0;

        LOG_DBG((LOG_SYSDEP, 50,
           "pf_key_v2_flow: src %s %s dst %s %s proto %u sport %u dport %u",
         laddr_str ? laddr_str : "<??\?>", lmask_str ? lmask_str : "<??\?>",
         raddr_str ? raddr_str : "<??\?>", rmask_str ? rmask_str : "<??\?>",
                 tproto, ntohs(sport), ntohs(dport)));

        free(laddr_str);
        free(lmask_str);
        free(raddr_str);
        free(rmask_str);

        ret = pf_key_v2_call(flow);
        pf_key_v2_msg_free(flow);
        flow = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                if (err == ESRCH)       /* These are common and usually
                                         * harmless.  */
                        LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_flow: %sFLOW: %s",
                                 delete ? "DEL" : "ADD", strerror(err)));
                else
                        log_print("pf_key_v2_flow: %sFLOW: %s",
                            delete ? "DEL" : "ADD", strerror(err));
                goto cleanup;
        }
        pf_key_v2_msg_free(ret);

        LOG_DBG((LOG_MISC, 50, "pf_key_v2_flow: %sFLOW: done",
                 delete ? "DEL" : "ADD"));

        return 0;

cleanup:
        free(sid);
        free(addr);
        if (flow)
                pf_key_v2_msg_free(flow);
        if (ret)
                pf_key_v2_msg_free(ret);
        return -1;
}

static u_int8_t *
pf_key_v2_convert_id(u_int8_t *id, int idlen, size_t *reslen, int *idtype)
{
        u_int8_t       *addr, *res = 0;
        char            addrbuf[ADDRESS_MAX + 5];

        switch (id[0]) {
        case IPSEC_ID_FQDN:
                res = calloc(idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ,
                    sizeof(u_int8_t));
                if (!res)
                        return 0;

                *reslen = idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ;
                memcpy(res, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ, *reslen);
                *idtype = SADB_IDENTTYPE_FQDN;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: FQDN %.*s",
                    (int) *reslen, res));
                return res;

        case IPSEC_ID_USER_FQDN:
                res = calloc(idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ,
                    sizeof(u_int8_t));
                if (!res)
                        return 0;

                *reslen = idlen - ISAKMP_ID_DATA_OFF + ISAKMP_GEN_SZ;
                memcpy(res, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ, *reslen);
                *idtype = SADB_IDENTTYPE_USERFQDN;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: UFQDN %.*s",
                    (int) *reslen, res));
                return res;

        case IPSEC_ID_IPV4_ADDR:
                if (inet_ntop(AF_INET, id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ,
                    addrbuf, ADDRESS_MAX) == NULL)
                        return 0;
                *reslen = strlen(addrbuf) + 3;
                strlcat(addrbuf, "/32", ADDRESS_MAX + 5);
                res = (u_int8_t *) strdup(addrbuf);
                if (!res)
                        return 0;
                *idtype = SADB_IDENTTYPE_PREFIX;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
                    "IPv4 address %s", res));
                return res;

        case IPSEC_ID_IPV6_ADDR:
                if (inet_ntop(AF_INET6,
                    id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ,
                    addrbuf, ADDRESS_MAX) == NULL)
                        return 0;
                *reslen = strlen(addrbuf) + 4;
                strlcat(addrbuf, "/128", ADDRESS_MAX + 5);
                res = (u_int8_t *) strdup(addrbuf);
                if (!res)
                        return 0;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
                    "IPv6 address %s", res));
                *idtype = SADB_IDENTTYPE_PREFIX;
                return res;

        case IPSEC_ID_IPV4_ADDR_SUBNET: /* XXX PREFIX */
                addr = id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
                if (inet_ntop(AF_INET, addr, addrbuf, ADDRESS_MAX) == NULL)
                        return 0;
                snprintf(addrbuf + strlen(addrbuf),
                    ADDRESS_MAX - strlen(addrbuf), "/%d",
                    pf_key_v2_mask_to_bits(*(u_int32_t *)(addr +
                        sizeof(struct in_addr))));
                *reslen = strlen(addrbuf);
                res = (u_int8_t *) strdup(addrbuf);
                if (!res)
                        return 0;
                *idtype = SADB_IDENTTYPE_PREFIX;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
                    "IPv4 subnet %s", res));
                return res;

        case IPSEC_ID_IPV6_ADDR_SUBNET: /* XXX PREFIX */
                addr = id + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
                if (inet_ntop(AF_INET6, addr, addrbuf, ADDRESS_MAX) == NULL)
                        return 0;
                snprintf(addrbuf + strlen(addrbuf),
                    ADDRESS_MAX - strlen(addrbuf), "/%d",
                    pf_key_v2_mask6_to_bits(addr +
                        sizeof(struct in6_addr)));
                *reslen = strlen(addrbuf);
                res = (u_int8_t *) strdup(addrbuf);
                if (!res)
                        return 0;
                LOG_DBG((LOG_SYSDEP, 40, "pf_key_v2_convert_id: "
                    "IPv6 subnet %s", res));
                *idtype = SADB_IDENTTYPE_PREFIX;
                return res;

        case IPSEC_ID_IPV4_RANGE:
        case IPSEC_ID_IPV6_RANGE:
        case IPSEC_ID_DER_ASN1_DN:
        case IPSEC_ID_DER_ASN1_GN:
        case IPSEC_ID_KEY_ID:
                /* XXX Not implemented yet.  */
                return 0;
        }

        return 0;
}

/* Enable a flow given an SA.  */
int
pf_key_v2_enable_sa(struct sa *sa, struct sa *isakmp_sa)
{
        struct ipsec_sa *isa = sa->data;
        struct sockaddr *dst, *src;
        int             error;
        struct proto   *proto = TAILQ_FIRST(&sa->protos);
        int             sidtype = 0, didtype = 0;
        size_t          sidlen = 0, didlen = 0;
        u_int8_t       *sid = 0, *did = 0;

        if (proto == NULL) {
                log_print("pf_key_v2_enable_sa: no proto");
                return EINVAL;
        }

        sa->transport->vtbl->get_dst(sa->transport, &dst);
        sa->transport->vtbl->get_src(sa->transport, &src);

        if (isakmp_sa->id_i) {
                if (isakmp_sa->initiator)
                        sid = pf_key_v2_convert_id(isakmp_sa->id_i,
                            isakmp_sa->id_i_len, &sidlen, &sidtype);
                else
                        did = pf_key_v2_convert_id(isakmp_sa->id_i,
                            isakmp_sa->id_i_len, &didlen, &didtype);
        }
        if (isakmp_sa->id_r) {
                if (isakmp_sa->initiator)
                        did = pf_key_v2_convert_id(isakmp_sa->id_r,
                            isakmp_sa->id_r_len, &didlen, &didtype);
                else
                        sid = pf_key_v2_convert_id(isakmp_sa->id_r,
                            isakmp_sa->id_r_len, &sidlen, &sidtype);
        }

        error = pf_key_v2_flow(isa->src_net, isa->src_mask, isa->dst_net,
            isa->dst_mask, isa->tproto, isa->sport, isa->dport, proto->spi[0],
            proto->proto, dst, src, 0, 0, sidtype, sid, sidlen, didtype, did,
            didlen, proto->data);
        if (error)
                goto cleanup;

        error = pf_key_v2_flow(isa->dst_net, isa->dst_mask, isa->src_net,
            isa->src_mask, isa->tproto, isa->dport, isa->sport, proto->spi[1],
            proto->proto, src, dst, 0, 1, sidtype, sid, sidlen, didtype, did,
            didlen, proto->data);

cleanup:
        free(sid);
        free(did);

        return error;
}

/* Increase reference count of refcounted sections. */
static int
pf_key_v2_conf_refinc(int af, char *section)
{
        char            conn[22];
        int             num;

        if (!section)
                return 0;

        num = conf_get_num(section, "Refcount", 0);
        if (num == 0)
                return 0;

        snprintf(conn, sizeof conn, "%d", num + 1);
        conf_set(af, section, "Refcount", conn, 1, 0);
        return 0;
}

/*
 * Return 0 if the section didn't exist or was removed, non-zero otherwise.
 * Don't touch non-refcounted (statically defined) sections.
 */
static int
pf_key_v2_conf_refhandle(int af, char *section)
{
        char            conn[22];
        int             num;

        if (!section)
                return 0;

        num = conf_get_num(section, "Refcount", 0);
        if (num == 1) {
                conf_remove_section(af, section);
                num--;
        } else if (num != 0) {
                snprintf(conn, sizeof conn, "%d", num - 1);
                conf_set(af, section, "Refcount", conn, 1, 0);
        }
        return num;
}

/* Remove all dynamically-established configuration entries.  */
static int
pf_key_v2_remove_conf(char *section)
{
        char           *ikepeer, *localid, *remoteid, *configname;
        struct conf_list_node *attr;
        struct conf_list *attrs;
        int             af;

        if (!section)
                return 0;

        if (!conf_get_str(section, "Phase"))
                return 0;

        /* Only remove dynamically-established entries. */
        attrs = conf_get_list(section, "Flags");
        if (attrs) {
                for (attr = TAILQ_FIRST(&attrs->fields); attr;
                    attr = TAILQ_NEXT(attr, link))
                        if (!strcasecmp(attr->field, "__ondemand"))
                                goto passed;

                conf_free_list(attrs);
        }
        return 0;

passed:
        conf_free_list(attrs);

        af = conf_begin();

        configname = conf_get_str(section, "Configuration");
        pf_key_v2_conf_refhandle(af, configname);

        /* These are the Phase 2 Local/Remote IDs. */
        localid = conf_get_str(section, "Local-ID");
        pf_key_v2_conf_refhandle(af, localid);

        remoteid = conf_get_str(section, "Remote-ID");
        pf_key_v2_conf_refhandle(af, remoteid);

        ikepeer = conf_get_str(section, "ISAKMP-peer");

        pf_key_v2_conf_refhandle(af, section);

        if (ikepeer) {
                remoteid = conf_get_str(ikepeer, "Remote-ID");
                localid = conf_get_str(ikepeer, "ID");
                configname = conf_get_str(ikepeer, "Configuration");

                pf_key_v2_conf_refhandle(af, ikepeer);
                pf_key_v2_conf_refhandle(af, configname);

                /* Phase 1 IDs */
                pf_key_v2_conf_refhandle(af, localid);
                pf_key_v2_conf_refhandle(af, remoteid);
        }
        conf_end(af, 1);
        return 0;
}

/* Disable a flow given a SA.  */
int
pf_key_v2_disable_sa(struct sa *sa, int incoming)
{
        struct ipsec_sa *isa = sa->data;
        struct sockaddr *dst, *src;
        struct proto   *proto = TAILQ_FIRST(&sa->protos);

        sa->transport->vtbl->get_dst(sa->transport, &dst);
        sa->transport->vtbl->get_src(sa->transport, &src);

        if (!incoming)
                return pf_key_v2_flow(isa->src_net, isa->src_mask,
                    isa->dst_net, isa->dst_mask, isa->tproto, isa->sport,
                    isa->dport, proto->spi[0], proto->proto, src, dst, 1, 0,
                    0, 0, 0, 0, 0, 0, proto->data);
        else {
                return pf_key_v2_flow(isa->dst_net, isa->dst_mask,
                    isa->src_net, isa->src_mask, isa->tproto, isa->dport,
                    isa->sport, proto->spi[1], proto->proto, src, dst, 1, 1,
                    0, 0, 0, 0, 0, 0, proto->data);
        }
}

/*
 * Delete the IPsec SA represented by the INCOMING direction in protocol PROTO
 * of the IKE security association SA.  Also delete potential flows tied to it.
 */
int
pf_key_v2_delete_spi(struct sa *sa, struct proto *proto, int incoming)
{
        struct sadb_msg msg;
        struct sadb_sa  ssa;
        struct sadb_address *addr = 0;
        struct sockaddr *saddr;
        int             len, err;
        struct pf_key_v2_msg *delete = 0, *ret = 0;

        /* If it's not an established SA, don't proceed. */
        if (!(sa->flags & SA_FLAG_READY))
                return 0;

        if (sa->name && !(sa->flags & SA_FLAG_REPLACED)) {
                LOG_DBG((LOG_SYSDEP, 50,
                         "pf_key_v2_delete_spi: removing configuration %s",
                         sa->name));
                pf_key_v2_remove_conf(sa->name);
        }
        msg.sadb_msg_type = SADB_DELETE;
        switch (proto->proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_delete_spi: invalid proto %d",
                    proto->proto);
                goto cleanup;
        }
        msg.sadb_msg_seq = 0;
        delete = pf_key_v2_msg_new(&msg, 0);
        if (!delete)
                goto cleanup;

        /* Setup the SA extension.  */
        ssa.sadb_sa_exttype = SADB_EXT_SA;
        ssa.sadb_sa_len = sizeof ssa / PF_KEY_V2_CHUNK;
        memcpy(&ssa.sadb_sa_spi, proto->spi[incoming], sizeof ssa.sadb_sa_spi);
        ssa.sadb_sa_replay = 0;
        ssa.sadb_sa_state = 0;
        ssa.sadb_sa_auth = 0;
        ssa.sadb_sa_encrypt = 0;
        ssa.sadb_sa_flags = 0;
        if (pf_key_v2_msg_add(delete, (struct sadb_ext *)&ssa, 0) == -1)
                goto cleanup;

        /*
         * Setup the ADDRESS extensions.
         */
        if (incoming)
                sa->transport->vtbl->get_dst(sa->transport, &saddr);
        else
                sa->transport->vtbl->get_src(sa->transport, &saddr);
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, saddr, SA_LEN(saddr));
        switch (saddr->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(delete, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        if (incoming)
                sa->transport->vtbl->get_src(sa->transport, &saddr);
        else
                sa->transport->vtbl->get_dst(sa->transport, &saddr);
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, saddr, SA_LEN(saddr));
        switch (saddr->sa_family) {
        case AF_INET:
                ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
                break;
        case AF_INET6:
                ((struct sockaddr_in6 *) (addr + 1))->sin6_port = 0;
                break;
        }
        if (pf_key_v2_msg_add(delete, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        ret = pf_key_v2_call(delete);
        pf_key_v2_msg_free(delete);
        delete = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_delete_spi: DELETE: %s",
                         strerror(err)));
                goto cleanup;
        }
        pf_key_v2_msg_free(ret);

        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_delete_spi: done"));

        return 0;

cleanup:
        free(addr);
        if (delete)
                pf_key_v2_msg_free(delete);
        if (ret)
                pf_key_v2_msg_free(ret);
        return -1;
}

static void
pf_key_v2_stayalive(struct exchange *exchange, void *vconn, int fail)
{
        char           *conn = vconn;
        struct sa      *sa;

        /* XXX What if it is phase 1 ? */
        sa = sa_lookup_by_name(conn, 2);
        if (sa)
                sa->flags |= SA_FLAG_STAYALIVE;

        /*
         * Remove failed configuration entry -- call twice because it is
         * created with a Refcount of 2.
         */
        if (fail && (!exchange || exchange->name)) {
                pf_key_v2_remove_conf(conn);
                pf_key_v2_remove_conf(conn);
        }
        free(conn);
}

/* Check if a connection CONN exists, otherwise establish it.  */
void
pf_key_v2_connection_check(char *conn)
{
        if (!sa_lookup_by_name(conn, 2)) {
                LOG_DBG((LOG_SYSDEP, 70,
                    "pf_key_v2_connection_check: SA for %s missing", conn));
                exchange_establish(conn, pf_key_v2_stayalive, conn, 0);
        } else {
                LOG_DBG((LOG_SYSDEP, 70, "pf_key_v2_connection_check: "
                    "SA for %s exists", conn));
                free(conn);
        }
}

/* Handle a PF_KEY lifetime expiration message PMSG.  */
static void
pf_key_v2_expire(struct pf_key_v2_msg *pmsg)
{
        struct sadb_msg *msg;
        struct sadb_sa *ssa;
        struct sadb_address *dst;
        struct sockaddr *dstaddr;
        struct sadb_lifetime *life, *lifecurrent;
        struct sa      *sa;
        struct pf_key_v2_node *lifenode, *ext;
        char           *dst_str;

        msg = (struct sadb_msg *)TAILQ_FIRST(pmsg)->seg;
        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_SA);
        if (!ext) {
                log_print("pf_key_v2_expire: no SA extension found");
                return;
        }
        ssa = ext->seg;
        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_DST);
        if (!ext) {
                log_print("pf_key_v2_expire: "
                    "no destination address extension found");
                return;
        }
        dst = ext->seg;
        dstaddr = (struct sockaddr *) (dst + 1);
        lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_HARD);
        if (!lifenode)
                lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_SOFT);
        if (!lifenode) {
                log_print("pf_key_v2_expire: no lifetime extension found");
                return;
        }
        life = lifenode->seg;

        lifenode = pf_key_v2_find_ext(pmsg, SADB_EXT_LIFETIME_CURRENT);
        if (!lifenode) {
                log_print("pf_key_v2_expire: "
                    "no current lifetime extension found");
                return;
        }
        lifecurrent = lifenode->seg;

        if (sockaddr2text(dstaddr, &dst_str, 0))
                dst_str = 0;

        LOG_DBG((LOG_SYSDEP, 20, "pf_key_v2_expire: "
            "%s dst %s SPI %x sproto %d",
            life->sadb_lifetime_exttype == SADB_EXT_LIFETIME_SOFT ? "SOFT"
            : "HARD", dst_str ? dst_str : "<unknown>",
            ntohl(ssa->sadb_sa_spi), msg->sadb_msg_satype));

        free(dst_str);

        /*
         * Find the IPsec SA.  The IPsec stack has two SAs for every IKE SA,
         * one outgoing and one incoming, we regard expirations for any of
         * them as an expiration of the full IKE SA.  Likewise, in
         * protection suites consisting of more than one protocol, any
         * expired individual IPsec stack SA will be seen as an expiration
         * of the full suite.
         */
        switch (msg->sadb_msg_satype) {
        case SADB_SATYPE_ESP:
                sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
                    IPSEC_PROTO_IPSEC_ESP);
                break;

        case SADB_SATYPE_AH:
                sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
                    IPSEC_PROTO_IPSEC_AH);
                break;

        case SADB_X_SATYPE_IPCOMP:
                sa = ipsec_sa_lookup(dstaddr, ssa->sadb_sa_spi,
                    IPSEC_PROTO_IPCOMP);
                break;

        default:
                /* XXX Log? */
                sa = 0;
                break;
        }

        /* If the SA is already gone, don't do anything.  */
        if (!sa)
                return;

        /*
         * If we got a notification, try to renegotiate the SA -- unless of
         * course it has already been replaced by another.
         * Also, ignore SAs that were not dynamically established, or that
         * did not see any use.
         */
        if (!(sa->flags & SA_FLAG_REPLACED) &&
            (sa->flags & SA_FLAG_ONDEMAND) &&
            lifecurrent->sadb_lifetime_bytes)
                exchange_establish(sa->name, 0, 0, 0);

        if (life->sadb_lifetime_exttype == SADB_EXT_LIFETIME_HARD) {
                /* Remove the old SA, it isn't useful anymore.  */
                sa_free(sa);
        }
}

static int
mask4len(const struct sockaddr_in *mask)
{
        int len;
        u_int32_t m;

        len = 0;
        for (m = 0x80000000; m & ntohl(mask->sin_addr.s_addr); m >>= 1)
                len++;
        if (len == 32)
                len = -1;
        return len;
}

#ifndef s6_addr8
#define s6_addr8 __u6_addr.__u6_addr8
#endif

static int
mask6len(const struct sockaddr_in6 *mask)
{
        int i, len;
        u_int8_t m;

        len = 0;
        for (i = 0, m = 0; i < 16 && !m; i++)
                for (m = 0x80; m & mask->sin6_addr.s6_addr8[i]; m >>= 1)
                        len++;
        if (len == 128)
                len = -1;
        return len;
}

static int
phase2id(char *str, size_t size, const char *side, const char *sflow,
    int masklen, u_int8_t proto, u_int16_t port)
{
        char smasklen[10], sproto[10], sport[10];

        smasklen[0] = sproto[0] = sport[0] = 0;
        if (masklen != -1)
                snprintf(smasklen, sizeof smasklen, "/%d", masklen);
        if (proto)
                snprintf(sproto, sizeof sproto, "=%u", proto);
        if (port)
                snprintf(sport, sizeof sport, ":%u", ntohs(port));

        return snprintf(str, size, "%s-%s%s%s%s", side, sflow, smasklen,
            sproto, sport);
}

/* Handle a PF_KEY SA ACQUIRE message PMSG.  */
static void
pf_key_v2_acquire(struct pf_key_v2_msg *pmsg)
{
        struct sadb_msg *msg, askpolicy_msg;
        struct pf_key_v2_msg *askpolicy = 0, *ret = 0;
        struct sadb_x_policy policy;
        struct sadb_address *dst = 0, *src = 0;
        struct sockaddr *dstaddr, *srcaddr = 0;
        struct sadb_ident *srcident = 0, *dstident = 0;
        char            dstbuf[ADDRESS_MAX], srcbuf[ADDRESS_MAX], *peer = 0;
        char            confname[120], *conn = 0;
        char           *srcid = 0, *dstid = 0, *prefstring = 0;
        int             slen, af, afamily, masklen;
        struct sockaddr *smask, *sflow, *dmask, *dflow;
        struct sadb_protocol *sproto;
        char            ssflow[ADDRESS_MAX], sdflow[ADDRESS_MAX];
        char            sdmask[ADDRESS_MAX], ssmask[ADDRESS_MAX];
        int             dmasklen, smasklen;
        char           *sidtype = 0, *didtype = 0;
        char            lname[100], dname[100], configname[200];
        int             shostflag = 0, dhostflag = 0;
        struct pf_key_v2_node *ext;
        struct passwd  *pwd = 0;
        u_int16_t       sport = 0, dport = 0;
        u_int8_t        tproto = 0;
        char            tmbuf[sizeof sport * 3 + 1], *xform;
        int             connlen;

        /* This needs to be dynamically allocated. */
        connlen = 22;
        conn = malloc(connlen);
        if (!conn) {
                log_error("pf_key_v2_acquire: malloc (%d) failed", connlen);
                return;
        }
        msg = (struct sadb_msg *)TAILQ_FIRST(pmsg)->seg;

        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_DST);
        if (!ext) {
                log_print("pf_key_v2_acquire: "
                    "no destination address specified");
                free(conn);
                return;
        }
        dst = ext->seg;

        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_ADDRESS_SRC);
        if (ext)
                src = ext->seg;

        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_IDENTITY_SRC);
        if (ext)
                srcident = ext->seg;

        ext = pf_key_v2_find_ext(pmsg, SADB_EXT_IDENTITY_DST);
        if (ext)
                dstident = ext->seg;

        /* Ask the kernel for the matching policy. */
        bzero(&askpolicy_msg, sizeof askpolicy_msg);
        askpolicy_msg.sadb_msg_type = SADB_X_ASKPOLICY;
        askpolicy = pf_key_v2_msg_new(&askpolicy_msg, 0);
        if (!askpolicy)
                goto fail;

        policy.sadb_x_policy_exttype = SADB_X_EXT_POLICY;
        policy.sadb_x_policy_len = sizeof policy / PF_KEY_V2_CHUNK;
        policy.sadb_x_policy_seq = msg->sadb_msg_seq;
        if (pf_key_v2_msg_add(askpolicy, (struct sadb_ext *)&policy, 0) == -1)
                goto fail;

        ret = pf_key_v2_call(askpolicy);
        if (!ret)
                goto fail;

        /* Now we have all the information needed. */

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_SRC_FLOW);
        if (!ext) {
                log_print("pf_key_v2_acquire: no source flow extension found");
                goto fail;
        }
        sflow = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_DST_FLOW);
        if (!ext) {
                log_print("pf_key_v2_acquire: "
                    "no destination flow extension found");
                goto fail;
        }
        dflow = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);
        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_SRC_MASK);
        if (!ext) {
                log_print("pf_key_v2_acquire: no source mask extension found");
                goto fail;
        }
        smask = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_DST_MASK);
        if (!ext) {
                log_print("pf_key_v2_acquire: "
                    "no destination mask extension found");
                goto fail;
        }
        dmask = (struct sockaddr *) (((struct sadb_address *) ext->seg) + 1);

        ext = pf_key_v2_find_ext(ret, SADB_X_EXT_FLOW_TYPE);
        if (!ext) {
                log_print("pf_key_v2_acquire: no flow type extension found");
                goto fail;
        }
        sproto = ext->seg;
        tproto = sproto->sadb_protocol_proto;

        bzero(ssflow, sizeof ssflow);
        bzero(sdflow, sizeof sdflow);
        bzero(ssmask, sizeof ssmask);
        bzero(sdmask, sizeof sdmask);
        smasklen = dmasklen = -1;

        sidtype = didtype = "IPV4_ADDR_SUBNET"; /* default */

        switch (sflow->sa_family) {
        case AF_INET:
                if (inet_ntop(AF_INET,
                    &((struct sockaddr_in *) sflow)->sin_addr, ssflow,
                    ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                sport = ((struct sockaddr_in *) sflow)->sin_port;
                if (inet_ntop(AF_INET,
                    &((struct sockaddr_in *) dflow)->sin_addr, sdflow,
                    ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                dport = ((struct sockaddr_in *) dflow)->sin_port;
                if (inet_ntop(AF_INET,
                    &((struct sockaddr_in *) smask)->sin_addr, ssmask,
                    ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                if (inet_ntop(AF_INET,
                    &((struct sockaddr_in *) dmask)->sin_addr, sdmask,
                    ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                smasklen = mask4len((struct sockaddr_in *) smask);
                dmasklen = mask4len((struct sockaddr_in *) dmask);
                if (((struct sockaddr_in *) smask)->sin_addr.s_addr ==
                    INADDR_BROADCAST) {
                        shostflag = 1;
                        sidtype = "IPV4_ADDR";
                }
                if (((struct sockaddr_in *) dmask)->sin_addr.s_addr ==
                    INADDR_BROADCAST) {
                        dhostflag = 1;
                        didtype = "IPV4_ADDR";
                }
                break;

        case AF_INET6:
                if (inet_ntop(AF_INET6,
                    &((struct sockaddr_in6 *) sflow)->sin6_addr,
                    ssflow, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                sport = ((struct sockaddr_in6 *) sflow)->sin6_port;
                if (inet_ntop(AF_INET6,
                    &((struct sockaddr_in6 *) dflow)->sin6_addr,
                    sdflow, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                dport = ((struct sockaddr_in6 *) dflow)->sin6_port;
                if (inet_ntop(AF_INET6,
                    &((struct sockaddr_in6 *) smask)->sin6_addr,
                    ssmask, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                if (inet_ntop(AF_INET6,
                    &((struct sockaddr_in6 *) dmask)->sin6_addr,
                    sdmask, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                smasklen = mask6len((struct sockaddr_in6 *) smask);
                dmasklen = mask6len((struct sockaddr_in6 *) dmask);
                sidtype = didtype = "IPV6_ADDR_SUBNET";
                if (IN6_IS_ADDR_FULL(&((struct sockaddr_in6 *)smask)->sin6_addr)) {
                        shostflag = 1;
                        sidtype = "IPV6_ADDR";
                }
                if (IN6_IS_ADDR_FULL(&((struct sockaddr_in6 *)dmask)->sin6_addr)) {
                        dhostflag = 1;
                        didtype = "IPV6_ADDR";
                }
                break;
        }

        dstaddr = (struct sockaddr *)(dst + 1);
        bzero(dstbuf, sizeof dstbuf);
        bzero(srcbuf, sizeof srcbuf);

        if (dstaddr->sa_family == 0) {
                /*
                 * Destination was not specified in the flow -- can we derive
                 * it?
                 */
                if (dhostflag == 0) {
                        log_print("pf_key_v2_acquire: "
                            "Cannot determine precise destination");
                        goto fail;
                }
                dstaddr = dflow;
        }
        switch (dstaddr->sa_family) {
        case AF_INET:
                if (inet_ntop(AF_INET,
                    &((struct sockaddr_in *) dstaddr)->sin_addr,
                    dstbuf, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                LOG_DBG((LOG_SYSDEP, 20,
                    "pf_key_v2_acquire: dst=%s sproto %d", dstbuf,
                    msg->sadb_msg_satype));
                break;

        case AF_INET6:
                if (inet_ntop(AF_INET6,
                    &((struct sockaddr_in6 *) dstaddr)->sin6_addr,
                    dstbuf, ADDRESS_MAX) == NULL) {
                        log_print("pf_key_v2_acquire: inet_ntop failed");
                        goto fail;
                }
                LOG_DBG((LOG_SYSDEP, 20,
                    "pf_key_v2_acquire: dst=%s sproto %d", dstbuf,
                    msg->sadb_msg_satype));
                break;
        }

        if (src) {
                srcaddr = (struct sockaddr *) (src + 1);

                switch (srcaddr->sa_family) {
                case AF_INET:
                        if (inet_ntop(AF_INET,
                            &((struct sockaddr_in *) srcaddr)->sin_addr,
                            srcbuf, ADDRESS_MAX) == NULL) {
                                log_print("pf_key_v2_acquire: "
                                    "inet_ntop failed");
                                goto fail;
                        }
                        break;

                case AF_INET6:
                        if (inet_ntop(AF_INET6,
                            &((struct sockaddr_in6 *)srcaddr)->sin6_addr,
                            srcbuf, ADDRESS_MAX) == NULL) {
                                log_print("pf_key_v2_acquire: "
                                    "inet_ntop failed");
                                goto fail;
                        }
                        break;

                default:
                        /*
                         * The kernel will pass an all '0' EXT_ADDRESS_SRC if
                         * it wasn't specified for the flow. In that case, do
                         * NOT specify the srcaddr in the Peer-name below
                         */
                        srcbuf[0] = 0;
                        srcaddr = NULL;
                        break;
                }
        }
        /* Insert source ID. */
        if (srcident) {
                slen = (srcident->sadb_ident_len * sizeof(u_int64_t))
                        - sizeof(struct sadb_ident);
                if (((unsigned char *) (srcident + 1))[slen - 1] != '\0') {
                        log_print("pf_key_v2_acquire: "
                            "source identity not NUL-terminated");
                        goto fail;
                }
                /* Check for valid type. */
                switch (srcident->sadb_ident_type) {
                case SADB_IDENTTYPE_PREFIX:
                        /* Determine what the address family is. */
                        srcid = memchr(srcident + 1, ':', slen);
                        if (srcid)
                                afamily = AF_INET6;
                        else
                                afamily = AF_INET;

                        srcid = memchr(srcident + 1, '/', slen);
                        if (!srcid) {
                                log_print("pf_key_v2_acquire: "
                                    "badly formatted PREFIX identity");
                                goto fail;
                        }
                        masklen = atoi(srcid + 1);

                        /* XXX We only support host addresses. */
                        if ((afamily == AF_INET6 && masklen != 128) ||
                            (afamily == AF_INET && masklen != 32)) {
                                log_print("pf_key_v2_acquire: "
                                    "non-host address specified in source "
                                    "identity (mask length %d), ignoring "
                                    "request", masklen);
                                goto fail;
                        }
                        /*
                         * NUL-terminate the PREFIX string at the separator,
                         * then dup.
                         */
                        *srcid = '\0';
                        if (asprintf(&srcid, "id-%s",
                            (char *) (srcident + 1)) == -1) {
                                log_error("pf_key_v2_acquire: asprintf() failed");
                                goto fail;
                        }

                        /* Set the section if it doesn't already exist. */
                        af = conf_begin();
                        if (!conf_get_str(srcid, "ID-type")) {
                                if (conf_set(af, srcid, "ID-type",
                                    afamily == AF_INET ? "IPV4_ADDR" :
                                    "IPV6_ADDR", 1, 0) ||
                                    conf_set(af, srcid, "Refcount", "1", 1, 0) ||
                                    conf_set(af, srcid, "Address",
                                        (char *) (srcident + 1), 1, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        } else
                                pf_key_v2_conf_refinc(af, srcid);
                        conf_end(af, 1);
                        break;

                case SADB_IDENTTYPE_FQDN:
                        prefstring = "FQDN";
                        /*FALLTHROUGH*/
                case SADB_IDENTTYPE_USERFQDN:
                        if (!prefstring) {
                                prefstring = "USER_FQDN";

                                /*
                                 * Check whether there is a string following
                                 * the header; if no, that there is a user ID
                                 * (and acquire the login name). If there is
                                 * both a string and a user ID, check that
                                 * they match.
                                 */
                                if ((slen == 0) &&
                                    (srcident->sadb_ident_id == 0)) {
                                        log_print("pf_key_v2_acquire: "
                                            "no user FQDN or ID provided");
                                        goto fail;
                                }
                                if (srcident->sadb_ident_id) {
                                        pwd =
                                            getpwuid(srcident->sadb_ident_id);
                                        if (!pwd) {
                                                log_error("pf_key_v2_acquire: "
                                                    "could not acquire "
                                                    "username from provided "
                                                    "ID %llu",
                                                    srcident->sadb_ident_id);
                                                goto fail;
                                        }
                                        if (slen != 0)
                                                if (strcmp(pwd->pw_name,
                                                    (char *) (srcident + 1))
                                                    != 0) {
                                                        log_print("pf_key_v2_acquire: "
                                                            "provided user "
                                                            "name and ID do "
                                                            "not match (%s != "
                                                            "%s)",
                                                            (char *) (srcident + 1),
                                                            pwd->pw_name);
                                                        /*
                                                         * String has
                                                         * precedence, per
                                                         * RFC 2367.
                                                         */
                                                }
                                }
                        }
                        if (asprintf(&srcid, "id-%s",
                            slen ? (char *) (srcident + 1) : pwd->pw_name) == -1) {
                                log_error("pf_key_v2_acquire: asprintf() failed");
                                goto fail;
                        }
                        pwd = 0;

                        /* Set the section if it doesn't already exist. */
                        af = conf_begin();
                        if (!conf_get_str(srcid, "ID-type")) {
                                if (conf_set(af, srcid, "ID-type", prefstring,
                                    1, 0) ||
                                    conf_set(af, srcid, "Refcount", "1", 1, 0) ||
                                    conf_set(af, srcid, "Name",
                                        srcid + 3, 1, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        } else
                                pf_key_v2_conf_refinc(af, srcid);
                        conf_end(af, 1);
                        break;

                default:
                        LOG_DBG((LOG_SYSDEP, 20,
                            "pf_key_v2_acquire: invalid source ID type %d",
                            srcident->sadb_ident_type));
                        goto fail;
                }

                LOG_DBG((LOG_SYSDEP, 50,
                    "pf_key_v2_acquire: constructed source ID \"%s\"", srcid));
                prefstring = 0;
        }
        /* Insert destination ID. */
        if (dstident) {
                slen = (dstident->sadb_ident_len * sizeof(u_int64_t))
                        - sizeof(struct sadb_ident);

                /* Check for valid type. */
                switch (dstident->sadb_ident_type) {
                case SADB_IDENTTYPE_PREFIX:
                        /* Determine what the address family is. */
                        dstid = memchr(dstident + 1, ':', slen);
                        if (dstid)
                                afamily = AF_INET6;
                        else
                                afamily = AF_INET;

                        dstid = memchr(dstident + 1, '/', slen);
                        if (!dstid) {
                                log_print("pf_key_v2_acquire: "
                                    "badly formatted PREFIX identity");
                                goto fail;
                        }
                        masklen = atoi(dstid + 1);

                        /* XXX We only support host addresses. */
                        if ((afamily == AF_INET6 && masklen != 128) ||
                            (afamily == AF_INET && masklen != 32)) {
                                log_print("pf_key_v2_acquire: "
                                    "non-host address specified in "
                                    "destination identity (mask length %d), "
                                    "ignoring request", masklen);
                                goto fail;
                        }
                        /*
                         * NUL-terminate the PREFIX string at the separator,
                         * then dup.
                         */
                        *dstid = '\0';
                        if (asprintf(&dstid, "id-%s",
                            (char *) (dstident + 1)) == -1) {
                                log_error("pf_key_v2_acquire: asprintf() failed");
                                goto fail;
                        }

                        /* Set the section if it doesn't already exist. */
                        af = conf_begin();
                        if (!conf_get_str(dstid, "ID-type")) {
                                if (conf_set(af, dstid, "ID-type",
                                    afamily == AF_INET ? "IPV4_ADDR" :
                                    "IPV6_ADDR", 1, 0) ||
                                    conf_set(af, dstid, "Refcount", "1", 1, 0) ||
                                    conf_set(af, dstid, "Address",
                                        (char *) (dstident + 1), 1, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        } else
                                pf_key_v2_conf_refinc(af, dstid);
                        conf_end(af, 1);
                        break;

                case SADB_IDENTTYPE_FQDN:
                        prefstring = "FQDN";
                        /*FALLTHROUGH*/
                case SADB_IDENTTYPE_USERFQDN:
                        if (!prefstring) {
                                prefstring = "USER_FQDN";

                                /*
                                 * Check whether there is a string following
                                 * the header; if no, that there is a user ID
                                 * (and acquire the login name). If there is
                                 * both a string and a user ID, check that
                                 * they match.
                                 */
                                if (slen == 0 &&
                                    dstident->sadb_ident_id == 0) {
                                        log_print("pf_key_v2_acquire: "
                                            "no user FQDN or ID provided");
                                        goto fail;
                                }
                                if (dstident->sadb_ident_id) {
                                        pwd = getpwuid(dstident->sadb_ident_id);
                                        if (!pwd) {
                                                log_error("pf_key_v2_acquire: "
                                                    "could not acquire "
                                                    "username from provided "
                                                    "ID %llu",
                                                    dstident->sadb_ident_id);
                                                goto fail;
                                        }
                                        if (slen != 0)
                                                if (strcmp(pwd->pw_name,
                                                    (char *) (dstident + 1))
                                                    != 0) {
                                                        log_print("pf_key_v2_acquire: "
                                                            "provided user "
                                                            "name and ID do "
                                                            "not match (%s != "
                                                            "%s)",
                                                            (char *) (dstident + 1),
                                                            pwd->pw_name);
                                                        /*
                                                         * String has
                                                         * precedence, per RF
                                                         * 2367.
                                                         */
                                                }
                                }
                        }
                        if (asprintf(&dstid, "id-%s",
                            slen ? (char *) (dstident + 1) : pwd->pw_name) == -1) {
                                log_error("pf_key_v2_acquire: asprintf() failed");
                                goto fail;
                        }
                        pwd = 0;

                        /* Set the section if it doesn't already exist. */
                        af = conf_begin();
                        if (!conf_get_str(dstid, "ID-type")) {
                                if (conf_set(af, dstid, "ID-type", prefstring,
                                    1, 0) ||
                                    conf_set(af, dstid, "Refcount", "1", 1, 0) ||
                                    conf_set(af, dstid, "Name",
                                        dstid + 3, 1, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        } else
                                pf_key_v2_conf_refinc(af, dstid);
                        conf_end(af, 1);
                        break;

                default:
                        LOG_DBG((LOG_SYSDEP, 20, "pf_key_v2_acquire: "
                            "invalid destination ID type %d",
                            dstident->sadb_ident_type));
                        goto fail;
                }

                LOG_DBG((LOG_SYSDEP, 50,
                    "pf_key_v2_acquire: constructed destination ID \"%s\"",
                    dstid));
        }
        /* Now we've placed the necessary IDs in the configuration space. */

        /* Get a new connection sequence number. */
        for (;; connection_seq++) {
                snprintf(conn, connlen, "Connection-%u", connection_seq);

                /* Does it exist ? */
                if (!conf_get_str(conn, "Phase"))
                        break;
        }

        /*
         * Set the IPsec connection entry. In particular, the following fields:
         * - Phase
         * - ISAKMP-peer
         * - Local-ID/Remote-ID (if provided)
         * - Acquire-ID (sequence number of kernel message, e.g., PF_KEYv2)
         * - Configuration
         *
         * Also set the following section:
         *    [peer-dstaddr(-local-srcaddr)]
         * with these fields:
         * - Phase
         * - ID (if provided)
         * - Remote-ID (if provided)
         * - Local-address (if provided)
         * - Address
         * - Configuration (if an entry phase1-dstaddr-srcadd)
         *                  exists -- otherwise use the defaults)
         */

        /*
         * The various cases:
         * - peer-dstaddr
         * - peer-dstaddr-local-srcaddr
         */
        if (asprintf(&peer, "peer-%s%s%s", dstbuf, srcaddr ? "-local-" : "",
            srcaddr ? srcbuf : "") == -1)
                goto fail;

        /*
         * Set the IPsec connection section. Refcount is set to 2, because
         * it will be linked both to the incoming and the outgoing SA.
         */
        af = conf_begin();
        if (conf_set(af, conn, "Phase", "2", 0, 0) ||
            conf_set(af, conn, "Flags", "__ondemand", 0, 0) ||
            conf_set(af, conn, "Refcount", "2", 0, 0) ||
            conf_set(af, conn, "ISAKMP-peer", peer, 0, 0)) {
                conf_end(af, 0);
                goto fail;
        }
        /* Set the sequence number. */
        snprintf(lname, sizeof lname, "%u", msg->sadb_msg_seq);
        if (conf_set(af, conn, "Acquire-ID", lname, 0, 0)) {
                conf_end(af, 0);
                goto fail;
        }
        /*
         * Set Phase 2 IDs -- this is the Local-ID section.
         * - from-address
         * - from-address=proto
         * - from-address=proto:port
         * - from-network/masklen
         * - from-network/masklen=proto
         * - from-network/masklen=proto:port
         */
        phase2id(lname, sizeof lname, "from", ssflow, smasklen, tproto, sport);
        if (conf_set(af, conn, "Local-ID", lname, 0, 0)) {
                conf_end(af, 0);
                goto fail;
        }
        if (!conf_get_str(lname, "ID-type")) {
                if (conf_set(af, lname, "Refcount", "1", 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                if (shostflag) {
                        if (conf_set(af, lname, "ID-type", sidtype, 0, 0) ||
                            conf_set(af, lname, "Address", ssflow, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                } else {
                        if (conf_set(af, lname, "ID-type", sidtype, 0, 0) ||
                            conf_set(af, lname, "Network", ssflow, 0, 0) ||
                            conf_set(af, lname, "Netmask", ssmask, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                }
                if (tproto) {
                        snprintf(tmbuf, sizeof sport * 3 + 1, "%u", tproto);
                        if (conf_set(af, lname, "Protocol", tmbuf, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                        if (sport) {
                                snprintf(tmbuf, sizeof sport * 3 + 1, "%u",
                                    ntohs(sport));
                                if (conf_set(af, lname, "Port", tmbuf, 0, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        }
                }
        } else
                pf_key_v2_conf_refinc(af, lname);

        /*
         * Set Remote-ID section.
         * to-address
         * to-address=proto
         * to-address=proto:port
         * to-network/masklen
         * to-network/masklen=proto
         * to-network/masklen=proto:port
         */
        phase2id(dname, sizeof dname, "to", sdflow, dmasklen, tproto, dport);
        if (conf_set(af, conn, "Remote-ID", dname, 0, 0)) {
                conf_end(af, 0);
                goto fail;
        }
        if (!conf_get_str(dname, "ID-type")) {
                if (conf_set(af, dname, "Refcount", "1", 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                if (dhostflag) {
                        if (conf_set(af, dname, "ID-type", didtype, 0, 0) ||
                            conf_set(af, dname, "Address", sdflow, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                } else {
                        if (conf_set(af, dname, "ID-type", didtype, 0, 0) ||
                            conf_set(af, dname, "Network", sdflow, 0, 0) ||
                            conf_set(af, dname, "Netmask", sdmask, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                }

                if (tproto) {
                        snprintf(tmbuf, sizeof dport * 3 + 1, "%u", tproto);
                        if (conf_set(af, dname, "Protocol", tmbuf, 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                        if (dport) {
                                snprintf(tmbuf, sizeof dport * 3 + 1, "%u",
                                    ntohs(dport));
                                if (conf_set(af, dname, "Port", tmbuf, 0, 0)) {
                                        conf_end(af, 0);
                                        goto fail;
                                }
                        }
                }
        } else
                pf_key_v2_conf_refinc(af, dname);

        /*
         * XXX
         * We should be using information from the proposal to set this up.
         * At least, we should make this selectable.
         */

        /*
         * Phase 2 configuration.
         * - phase2-from-address-to-address
         * - ...
         * - phase2-from-net/len=proto:port-to-net/len=proto:port
         */
        snprintf(configname, sizeof configname, "phase2-%s-%s", lname, dname);
        if (conf_set(af, conn, "Configuration", configname, 0, 0)) {
                conf_end(af, 0);
                goto fail;
        }
        if (!conf_get_str(configname, "Exchange_type")) {
                if (conf_set(af, configname, "Exchange_type", "Quick_mode",
                    0, 0) ||
                    conf_set(af, peer, "Refcount", "1", 0, 0) ||
                    conf_set(af, configname, "DOI", "IPSEC", 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                if (conf_get_str("General", "Default-phase-2-suites")) {
                        if (conf_set(af, configname, "Suites",
                            conf_get_str("General", "Default-phase-2-suites"),
                            0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                } else {
                        if (conf_set(af, configname, "Suites",
                            "QM-ESP-3DES-SHA-PFS-SUITE", 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                }
        } else 
                pf_key_v2_conf_refinc(af, configname);

        /* Set the ISAKMP-peer section. */
        if (!conf_get_str(peer, "Phase")) {
                if (conf_set(af, peer, "Phase", "1", 0, 0) ||
                    conf_set(af, peer, "Refcount", "1", 0, 0) ||
                    conf_set(af, peer, "Address", dstbuf, 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                if (srcaddr && conf_set(af, peer, "Local-address", srcbuf, 0,
                    0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                snprintf(confname, sizeof confname, "phase1-%s", peer);
                if (conf_set(af, peer, "Configuration", confname, 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }

                /* Phase 1 configuration. */
                if (!conf_get_str(confname, "exchange_type")) {
                        xform = conf_get_str("Default-phase-1-configuration",
                        "Transforms");
                        if (conf_set(af, confname, "Transforms", xform ? xform :
                            "3DES-SHA-RSA_SIG", 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }

                        if (conf_set(af, confname, "Exchange_Type", "ID_PROT",
                            0, 0) ||
                            conf_set(af, confname, "DOI", "IPSEC", 0, 0) ||
                            conf_set(af, confname, "Refcount", "1", 0, 0)) {
                                conf_end(af, 0);
                                goto fail;
                        }
                } else
                        pf_key_v2_conf_refinc(af, confname);

                /* The ID we should use in Phase 1. */
                if (srcid && conf_set(af, peer, "ID", srcid, 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
                /* The ID the other side should use in Phase 1. */
                if (dstid && conf_set(af, peer, "Remote-ID", dstid, 0, 0)) {
                        conf_end(af, 0);
                        goto fail;
                }
        } else
                pf_key_v2_conf_refinc(af, peer);

        /* All done. */
        conf_end(af, 1);

        /* Let's rock 'n roll. */
        connection_record_passive(conn);
        pf_key_v2_connection_check(conn);
        conn = 0;

        /* Fall-through to cleanup. */
fail:
        if (ret)
                pf_key_v2_msg_free(ret);
        if (askpolicy)
                pf_key_v2_msg_free(askpolicy);
        free(srcid);
        free(dstid);
        free(peer);
        free(conn);
        return;
}

static void
pf_key_v2_notify(struct pf_key_v2_msg *msg)
{
        switch (((struct sadb_msg *)TAILQ_FIRST(msg)->seg)->sadb_msg_type) {
        case SADB_EXPIRE:
                pf_key_v2_expire(msg);
                break;

        case SADB_ACQUIRE:
                if (!ui_daemon_passive)
                        pf_key_v2_acquire(msg);
                break;

        default:
                log_print("pf_key_v2_notify: unexpected message type (%d)",
                    ((struct sadb_msg *)TAILQ_FIRST(msg)->seg)->sadb_msg_type);
        }
        pf_key_v2_msg_free(msg);
}

void
pf_key_v2_handler(int fd)
{
        struct pf_key_v2_msg *msg;
        int             n;

        /*
         * As synchronous read/writes to the socket can have taken place
         * between the select(2) call of the main loop and this handler, we
         * need to recheck the readability.
         */
        if (ioctl(pf_key_v2_socket, FIONREAD, &n) == -1) {
                log_error("pf_key_v2_handler: ioctl (%d, FIONREAD, &n) failed",
                    pf_key_v2_socket);
                return;
        }
        if (!n)
                return;

        msg = pf_key_v2_read(0);
        if (msg)
                pf_key_v2_notify(msg);
}

/*
 * Group 2 IPsec SAs given by the PROTO1 and PROTO2 protocols of the SA IKE
 * security association in a chain.
 * XXX Assumes OpenBSD GRPSPIS extension.
 */
int
pf_key_v2_group_spis(struct sa *sa, struct proto *proto1,
    struct proto *proto2, int incoming)
{
        struct sadb_msg msg;
        struct sadb_sa  sa1, sa2;
        struct sadb_address *addr = 0;
        struct sadb_protocol protocol;
        struct pf_key_v2_msg *grpspis = 0, *ret = 0;
        struct sockaddr *saddr;
        int             err;
        size_t          len;

        msg.sadb_msg_type = SADB_X_GRPSPIS;
        switch (proto1->proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                msg.sadb_msg_satype = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                msg.sadb_msg_satype = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                msg.sadb_msg_satype = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_group_spis: invalid proto %d",
                    proto1->proto);
                goto cleanup;
        }
        msg.sadb_msg_seq = 0;
        grpspis = pf_key_v2_msg_new(&msg, 0);
        if (!grpspis)
                goto cleanup;

        /* Setup the SA extensions.  */
        sa1.sadb_sa_exttype = SADB_EXT_SA;
        sa1.sadb_sa_len = sizeof sa1 / PF_KEY_V2_CHUNK;
        memcpy(&sa1.sadb_sa_spi, proto1->spi[incoming],
            sizeof sa1.sadb_sa_spi);
        sa1.sadb_sa_replay = 0;
        sa1.sadb_sa_state = 0;
        sa1.sadb_sa_auth = 0;
        sa1.sadb_sa_encrypt = 0;
        sa1.sadb_sa_flags = 0;
        if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *)&sa1, 0) == -1)
                goto cleanup;

        sa2.sadb_sa_exttype = SADB_X_EXT_SA2;
        sa2.sadb_sa_len = sizeof sa2 / PF_KEY_V2_CHUNK;
        memcpy(&sa2.sadb_sa_spi, proto2->spi[incoming],
            sizeof sa2.sadb_sa_spi);
        sa2.sadb_sa_replay = 0;
        sa2.sadb_sa_state = 0;
        sa2.sadb_sa_auth = 0;
        sa2.sadb_sa_encrypt = 0;
        sa2.sadb_sa_flags = 0;
        if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *)&sa2, 0) == -1)
                goto cleanup;

        /*
         * Setup the ADDRESS extensions.
         */
        if (incoming)
                sa->transport->vtbl->get_src(sa->transport, &saddr);
        else
                sa->transport->vtbl->get_dst(sa->transport, &saddr);
        len = sizeof *addr + PF_KEY_V2_ROUND(SA_LEN(saddr));
        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, saddr, SA_LEN(saddr));
        ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
        if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        addr = calloc(1, len);
        if (!addr)
                goto cleanup;
        addr->sadb_address_exttype = SADB_X_EXT_DST2;
        addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
        addr->sadb_address_reserved = 0;
        memcpy(addr + 1, saddr, SA_LEN(saddr));
        ((struct sockaddr_in *) (addr + 1))->sin_port = 0;
        if (pf_key_v2_msg_add(grpspis, (struct sadb_ext *) addr,
            PF_KEY_V2_NODE_MALLOCED) == -1)
                goto cleanup;
        addr = 0;

        /* Setup the sa type extension.  */
        protocol.sadb_protocol_exttype = SADB_X_EXT_SATYPE2;
        protocol.sadb_protocol_len = sizeof protocol / PF_KEY_V2_CHUNK;
        switch (proto2->proto) {
        case IPSEC_PROTO_IPSEC_ESP:
                protocol.sadb_protocol_proto = SADB_SATYPE_ESP;
                break;
        case IPSEC_PROTO_IPSEC_AH:
                protocol.sadb_protocol_proto = SADB_SATYPE_AH;
                break;
        case IPSEC_PROTO_IPCOMP:
                protocol.sadb_protocol_proto = SADB_X_SATYPE_IPCOMP;
                break;
        default:
                log_print("pf_key_v2_group_spis: invalid proto %d",
                    proto2->proto);
                goto cleanup;
        }
        protocol.sadb_protocol_reserved2 = 0;
        if (pf_key_v2_msg_add(grpspis,
            (struct sadb_ext *)&protocol, 0) == -1)
                goto cleanup;

        ret = pf_key_v2_call(grpspis);
        pf_key_v2_msg_free(grpspis);
        grpspis = 0;
        if (!ret)
                goto cleanup;
        err = ((struct sadb_msg *)TAILQ_FIRST(ret)->seg)->sadb_msg_errno;
        if (err) {
                log_print("pf_key_v2_group_spis: GRPSPIS: %s", strerror(err));
                goto cleanup;
        }
        pf_key_v2_msg_free(ret);

        LOG_DBG((LOG_SYSDEP, 50, "pf_key_v2_group_spis: done"));

        return 0;

cleanup:
        free(addr);
        if (grpspis)
                pf_key_v2_msg_free(grpspis);
        if (ret)
                pf_key_v2_msg_free(ret);
        return -1;
}