[BACK]Return to pf_key_v2.c CVS log [TXT][DIR] Up to [local] / src / sbin / isakmpd

File: [local] / src / sbin / isakmpd / pf_key_v2.c (download)

Revision 1.34, Mon Oct 30 16:04:00 2000 UTC (23 years, 7 months ago) by angelos
Branch: MAIN
CVS Tags: OPENBSD_2_8_BASE
Branch point for: OPENBSD_2_8
Changes since 1.33: +8 -7 lines

Fix bug with ACQUIRE message.

/*	$OpenBSD: pf_key_v2.c,v 1.34 2000/10/30 16:04:00 angelos Exp $	*/
/*	$EOM: pf_key_v2.c,v 1.59 2000/10/16 18:16:59 provos Exp $	*/

/*
 * Copyright (c) 1999, 2000 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 1999, 2000 Angelos D. Keromytis.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Ericsson Radio Systems.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * 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/time.h>
#include <sys/uio.h>
#include <net/pfkeyv2.h>
#include <netinet/in.h>
#ifdef SADB_X_EXT_FLOW_TYPE
#include <sys/mbuf.h>
#include <netinet/ip_ipsp.h>
#endif
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "sysdep.h"

#include "conf.h"
#include "exchange.h"
#include "ipsec.h"
#include "ipsec_num.h"
#include "log.h"
#include "pf_key_v2.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"

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

#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;

#ifdef KAME
/*
 * KAME requires the sadb_msg_seq of an UPDATE be the same of that of the
 * GETSPI creating the larval SA.
 */
struct pf_key_v2_sa_seq {
  TAILQ_ENTRY (pf_key_v2_sa_seq) link;
  u_int8_t *spi;
  size_t sz;
  u_int8_t proto;
  struct sockaddr *dst;
  int dstlen;
  u_int32_t seq;
};

TAILQ_HEAD (, pf_key_v2_sa_seq) pf_key_v2_sa_seq_map;
#endif

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 *);

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

#ifdef KAME
static int
pf_key_v2_register_sa_seq (u_int8_t *spi, size_t sz, u_int8_t proto,
			   struct sockaddr *dst, int dstlen, u_int32_t seq)
{
  struct pf_key_v2_sa_seq *node = 0;

  node = malloc (sizeof *node);
  if (!node)
    goto cleanup;
  memset (node, '0', sizeof *node);
  node->spi = malloc (sz);
  if (!node->spi)
    goto cleanup;
  node->dst = malloc (dstlen);
  if (!node->spi)
    goto cleanup;
  memcpy (node->dst, dst, dstlen);
  node->dstlen = dstlen;
  memcpy (node->spi, spi, sz);
  node->sz = sz;
  node->proto = proto;
  node->seq = seq;
  TAILQ_INSERT_TAIL (&pf_key_v2_sa_seq_map, node, link);
  return 1;

 cleanup:
  if (node->dst)
    free (node->dst);
  if (node)
    free (node);
  return 0;
}

static u_int32_t
pf_key_v2_seq_by_sa (u_int8_t *spi, size_t sz, u_int8_t proto,
		     struct sockaddr *dst, int dstlen)
{
  struct pf_key_v2_sa_seq *node;

  for (node = TAILQ_FIRST (&pf_key_v2_sa_seq_map); node;
       node = TAILQ_NEXT (node, link))
    if (node->proto == proto
	&& node->sz == sz && memcmp (node->spi, spi, sz) == 0
	&& node->dstlen == dstlen && memcmp (node->dst, dst, dstlen) == 0)
      return node->seq;
  return 0;
}
#endif

static struct pf_key_v2_msg *
pf_key_v2_msg_new (struct sadb_msg *msg, int flags)
{
  struct pf_key_v2_node *node = 0;
  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:
  if (node)
    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, *next;

  for (np = TAILQ_FIRST (msg); np; np = next)
    {
      next = TAILQ_NEXT (np, link);
      if (np->flags & PF_KEY_V2_NODE_MALLOCED)
	free (np->seg);
      free (np);
    }
  free (msg);
}

/* Just return a new sequence number.  */
static u_int32_t
pf_key_v2_seq ()
{
  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 untile 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 timeval tv;
  fd_set *fds;

  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)
	{
	  fds = calloc (howmany (pf_key_v2_socket + 1, NFDBITS),
			sizeof (fd_mask));
	  if (!fds)
	    {
	      log_error ("pf_key_v2_read: calloc (%d, %d) failed",
			 howmany (pf_key_v2_socket + 1, NFDBITS),
			 sizeof (fd_mask));
	      goto cleanup;
	    }
	  FD_SET (pf_key_v2_socket, fds);
	  tv.tv_sec = 0;
	  tv.tv_usec = PF_KEY_REPLY_TIMEOUT;
	  n = select (pf_key_v2_socket + 1, fds, 0, 0, &tv);
	  free (fds);
	  if (n == -1)
	    {
	      log_error ("pf_key_v2_read: select (%d, fds, 0, 0, &tv) failed",
			 pf_key_v2_socket + 1);
	      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 "
		     "(%d bytes)",
		     pf_key_v2_socket, n);
	  goto cleanup;
	}

      n = hdr.sadb_msg_len * PF_KEY_V2_CHUNK;
      buf = malloc (n);
      if (!buf)
	{
	  log_error ("pf_key_v2_read: malloc (%d) failed", n);
	  goto cleanup;
	}

      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 ((size_t)n != hdr.sadb_msg_len * PF_KEY_V2_CHUNK)
	{
	  log_print ("pf_key_v2_read: read (%d, ...) returned short packet "
		     "(%d bytes)",
		     pf_key_v2_socket, 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 != 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 %d), ignored",
			msg->sadb_msg_version, msg->sadb_msg_pid,
			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 != getpid () || msg->sadb_msg_seq != seq))
	{
	  gettimeofday (&tv, 0);
	  timer_add_event ("pf_key_v2_notify",
			   (void (*) (void *))pf_key_v2_notify, ret, &tv);
	  ret = 0;
	  continue;
	}

      return ret;
    }

 cleanup:
  if (buf)
    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 = (struct iovec *)malloc (cnt * sizeof *iov);
  if (!iov)
    {
      log_error ("pf_key_v2_write: malloc (%d) failed", cnt * 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++)
    {
      sprintf (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));
    }

  n = writev (pf_key_v2_socket, iov, cnt);
  if (n == -1)
    {
      log_error ("pf_key_v2_write: writev (%d, 0x%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 (%d)",
		 pf_key_v2_socket, n);
      goto cleanup;
    }
  free (iov);
  return msg->sadb_msg_seq;

 cleanup:
  if (iov)
    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 ()
{
  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.  */

#ifdef KAME
  TAILQ_INIT (&pf_key_v2_sa_seq_map);
#endif

  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,
		   int srclen, struct sockaddr *dst, int dstlen)
{
  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;
#ifdef KAME
  struct sadb_x_sa2 ssa2;
#endif

  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;
    default:
      log_print ("pf_key_v2_get_spi: invalid proto %d", proto);
      goto cleanup;
    }
  /*
   * XXX When we have acquires working, the sequence number has to be set
   * from the acquire message.
   */
  msg.sadb_msg_seq = 0;
  getspi = pf_key_v2_msg_new (&msg, 0);
  if (!getspi)
    goto cleanup;

#ifdef KAME
  memset(&ssa2, 0, sizeof ssa2);
  ssa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
  ssa2.sadb_x_sa2_len = sizeof ssa2 / PF_KEY_V2_CHUNK;
  ssa2.sadb_x_sa2_mode = 0;
  if (pf_key_v2_msg_add (getspi, (struct sadb_ext *)&ssa2, 0) == -1)
    goto cleanup;
#endif

  /* Setup the ADDRESS extensions.  */
  len = sizeof (struct sadb_address) + PF_KEY_V2_ROUND (srclen); 
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, src, srclen);
  /* XXX IPv4-specific.  */
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  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 (dstlen); 
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, dst, dstlen);
  /* XXX IPv4-specific.  */
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  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;
  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;

  *sz = sizeof sa->sadb_sa_spi;
  spi = malloc (*sz);
  if (!spi)
    goto cleanup;
  memcpy (spi, &sa->sadb_sa_spi, *sz);
#ifdef KAME
  if (!pf_key_v2_register_sa_seq (spi, *sz, proto, dst, dstlen,
				  ((struct sadb_msg *)(TAILQ_FIRST (ret)->seg))
				  ->sadb_msg_seq))
    goto cleanup;
#endif
  pf_key_v2_msg_free (ret);

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

  return spi;

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

/*
 * 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 sadb_msg msg;
  struct sadb_sa ssa;
  struct sadb_lifetime *life = 0;
  struct sadb_address *addr = 0;
  struct sadb_key *key = 0;
  struct sockaddr *src, *dst;
  int dstlen, srclen, keylen, hashlen, err;
  struct pf_key_v2_msg *update = 0, *ret = 0;
  struct ipsec_proto *iproto = proto->data;
  size_t len;
#ifdef KAME
  struct sadb_x_sa2 ssa2;
#endif

  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_DES:
	case IPSEC_ESP_DES_IV32:
	case IPSEC_ESP_DES_IV64:
	  ssa.sadb_sa_encrypt = SADB_EALG_DESCBC;
	  break;

	case IPSEC_ESP_3DES:
	  ssa.sadb_sa_encrypt = SADB_EALG_3DESCBC;
	  break;

#ifdef SADB_X_EALG_AES
	case IPSEC_ESP_AES:
	  ssa.sadb_sa_encrypt = SADB_X_EALG_AES;
	  break;
#endif

#ifdef SADB_X_EALG_CAST
	case IPSEC_ESP_CAST:
	  ssa.sadb_sa_encrypt = SADB_X_EALG_CAST;
	  break;
#endif

#ifdef SADB_X_EALG_BLF
	case IPSEC_ESP_BLOWFISH:
	  ssa.sadb_sa_encrypt = SADB_X_EALG_BLF;
	  break;
#endif

	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:
#ifdef KAME
	  ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
#else
	  ssa.sadb_sa_auth = SADB_AALG_MD5HMAC96;
#endif	
	  break;

	case IPSEC_AUTH_HMAC_SHA:
#ifdef KAME
	  ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
#else
	  ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC96;
#endif
	  break;

#ifndef KAME
        case IPSEC_AUTH_HMAC_RIPEMD:
	  ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC96;
	  break;
#endif

	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:
#ifdef KAME
	  ssa.sadb_sa_auth = SADB_AALG_MD5HMAC;
#else
	  ssa.sadb_sa_auth = SADB_AALG_MD5HMAC96;
#endif
	  break;

	case IPSEC_AH_SHA:
#ifdef KAME
	  ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC;
#else
	  ssa.sadb_sa_auth = SADB_AALG_SHA1HMAC96;
#endif
	  break;

#ifndef KAME
	case IPSEC_AH_RIPEMD:
	  ssa.sadb_sa_auth = SADB_X_AALG_RIPEMD160HMAC96;
	  break;
#endif

	default:
	  LOG_DBG ((LOG_SYSDEP, 50, "pf_key_v2_set_spi: unknown authentication algorithm %d", proto->id));
	  goto cleanup;
	}
      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, &dstlen);
  else
    sa->transport->vtbl->get_dst (sa->transport, &dst, &dstlen);
#ifdef KAME
  msg.sadb_msg_seq
    = (incoming ? pf_key_v2_seq_by_sa (proto->spi[incoming],
				       sizeof ssa.sadb_sa_spi, proto->proto,
				       dst, dstlen)
       : 0);
#else
  msg.sadb_msg_seq = 0;
#endif
  update = pf_key_v2_msg_new (&msg, 0);
  if (!update)
    goto cleanup;

#ifdef KAME
  memset(&ssa2, 0, sizeof ssa2);
  ssa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
  ssa2.sadb_x_sa2_len = sizeof ssa2 / PF_KEY_V2_CHUNK;
  ssa2.sadb_x_sa2_mode = 0;
  if (pf_key_v2_msg_add (update, (struct sadb_ext *)&ssa2, 0) == -1)
    goto cleanup;
#endif

  /* Setup the rest of 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
    = conf_get_str ("General", "Shared-SADB") ? 0 : iproto->replay_window;
  ssa.sadb_sa_state = SADB_SASTATE_MATURE;
#ifdef SADB_X_SAFLAGS_TUNNEL
  ssa.sadb_sa_flags
    = iproto->encap_mode == IPSEC_ENCAP_TUNNEL ? SADB_X_SAFLAGS_TUNNEL : 0;
#else
  ssa.sadb_sa_flags = 0;
#endif
  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.
   *
   * XXX Addresses have to be thought through.  Assumes IPv4.
   */
  if (incoming)
    sa->transport->vtbl->get_dst (sa->transport, &src, &srclen);
  else
    sa->transport->vtbl->get_src (sa->transport, &src, &srclen);
  len = sizeof *addr + PF_KEY_V2_ROUND (srclen);
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, src, srclen);
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  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 (dstlen);
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, dst, dstlen);
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  if (pf_key_v2_msg_add (update, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

#if 0
  /* XXX I am not sure about what to do here just yet. */
  if (iproto->encap_mode == IPSEC_ENCAP_TUNNEL)
    {
      len = sizeof *addr + PF_KEY_V2_ROUND (dstlen);
      addr = malloc (len);
      if (!addr)
	goto cleanup;
      addr->sadb_address_exttype = SADB_EXT_ADDRESS_PROXY;
      addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
      addr->sadb_address_proto = 0;
      addr->sadb_address_prefixlen = 0;
#endif
      addr->sadb_address_reserved = 0;
      memcpy (addr + 1, dst, dstlen);
      ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
      if (pf_key_v2_msg_add (update, (struct sadb_ext *)addr,
			     PF_KEY_V2_NODE_MALLOCED) == -1)
	goto cleanup;
      addr = 0;
#if 0
      msg->em_odst = msg->em_dst;
      msg->em_osrc = msg->em_src;
#endif
    }
#endif

  /* Setup the KEY extensions.  */
  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;
    }

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

  /* XXX IPv4 specific.  */
  LOG_DBG ((LOG_SYSDEP, 10, "pf_key_v2_set_spi: satype %d dst %s SPI 0x%x",
	    msg.sadb_msg_satype,
	    inet_ntoa (((struct sockaddr_in *)dst)->sin_addr),
	    ntohl (ssa.sadb_sa_spi)));

  /*
   * 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:
  if (addr)
    free (addr);
  if (life)
    free (life);
  if (key)
    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) 
{
  return (33 - ffs (~mask + 1)) % 33;
}

/*
 * Enable/disable a flow.
 * XXX Assumes OpenBSD {ADD,DEL}FLOW extensions.
 * Should probably be moved to sysdep.c
 */
static int
pf_key_v2_flow (in_addr_t laddr, in_addr_t lmask, in_addr_t raddr,
		in_addr_t rmask, u_int8_t *spi, u_int8_t proto,
		in_addr_t dst, in_addr_t src, int delete, int ingress)
{
#if defined (SADB_X_ADDFLOW) && defined (SADB_X_DELFLOW)
  struct sadb_msg msg;
#ifdef SADB_X_EXT_FLOW_TYPE
  struct sadb_protocol flowtype;
#else
  struct sadb_sa ssa;
#endif
  struct sadb_address *addr = 0;
  struct pf_key_v2_msg *flow = 0, *ret = 0;
  size_t len;
  int err;

#if !defined (SADB_X_SAFLAGS_INGRESS_FLOW) && !defined(SADB_X_EXT_FLOW_TYPE)
  if (ingress)
    return 0;
#endif

  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;
    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;

#ifdef SADB_X_EXT_FLOW_TYPE
  /* Setup the flow type extension.  */
  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 = FLOW_X_TYPE_REQUIRE;

  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)&flowtype, 0) == -1)
    goto cleanup;
#else /* SADB_X_EXT_FLOW_TYPE */
  /* 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, spi, 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;
#ifdef SADB_X_SAFLAGS_INGRESS_FLOW
  if (ingress)
    ssa.sadb_sa_flags |= SADB_X_SAFLAGS_INGRESS_FLOW;
#endif
#ifdef SADB_X_SAFLAGS_REPLACEFLOW
  if (!delete && !ingress)
    ssa.sadb_sa_flags |= SADB_X_SAFLAGS_REPLACEFLOW;
#endif

  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)&ssa, 0) == -1)
    goto cleanup;
#endif /* SADB_X_EXT_FLOW_TYPE */

  /*
   * Setup the ADDRESS extensions.
   *
   * XXX Addresses have to be thought through.  Assumes IPv4.
   */
  len = sizeof *addr + PF_KEY_V2_ROUND (sizeof (struct sockaddr_in));
  if (!delete || ingress)
    {
      addr = malloc (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;
      memset (addr + 1, '\0', sizeof (struct sockaddr_in));
      ((struct sockaddr_in *)(addr + 1))->sin_len
	= sizeof (struct sockaddr_in);
      ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
#ifdef SADB_X_EXT_FLOW_TYPE
      ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr
	= ingress ? src : dst;
#else
      ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = dst;
#endif
      ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
      if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			     PF_KEY_V2_NODE_MALLOCED) == -1)
	goto cleanup;
      addr = 0;
    }

  addr = malloc (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;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = laddr;
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  addr = malloc (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;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = lmask;
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  addr = malloc (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;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = raddr;
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  addr = malloc (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;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = rmask;
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  LOG_DBG ((LOG_SYSDEP, 50, "pf_key_v2_flow: src %x %x dst %x %x",
	    ntohl (laddr), ntohl (lmask), ntohl (raddr), ntohl (rmask)));

  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: done"));

  return 0;

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

#elif defined (SADB_X_SPDADD) && defined (SADB_X_SPDDELETE)
  struct sadb_msg msg;
  struct sadb_x_policy *policy = 0;
  struct sadb_x_ipsecrequest *ipsecrequest;
  struct sadb_x_sa2 ssa2;
  struct sadb_address *addr = 0;
  struct sockaddr_in *saddr;
  u_int8_t
    policy_buf[sizeof *policy + sizeof *ipsecrequest + 2 * sizeof *saddr];
  struct pf_key_v2_msg *flow = 0, *ret = 0;
  size_t len;
  int err;

  msg.sadb_msg_type = delete ? SADB_X_SPDDELETE : SADB_X_SPDADD;
  msg.sadb_msg_satype = SADB_SATYPE_UNSPEC;
  msg.sadb_msg_seq = 0;
  flow = pf_key_v2_msg_new (&msg, 0);
  if (!flow)
    goto cleanup;

  memset(&ssa2, 0, sizeof ssa2);
  ssa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
  ssa2.sadb_x_sa2_len = sizeof ssa2 / PF_KEY_V2_CHUNK;
  ssa2.sadb_x_sa2_mode = 0;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)&ssa2, 0) == -1)
    goto cleanup;

  /*
   * Setup the ADDRESS extensions.
   *
   * XXX Addresses have to be thought through.  Assumes IPv4.
   */
  len = sizeof *addr + PF_KEY_V2_ROUND (sizeof (struct sockaddr_in));
  addr = malloc (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_proto = IPSEC_ULPROTO_ANY;
  addr->sadb_address_prefixlen = pf_key_v2_mask_to_bits (ntohl (lmask));
  addr->sadb_address_reserved = 0;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = laddr;
  ((struct sockaddr_in *)(addr + 1))->sin_port = IPSEC_PORT_ANY;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  addr = malloc (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_proto = IPSEC_ULPROTO_ANY;
  addr->sadb_address_prefixlen = pf_key_v2_mask_to_bits (ntohl (rmask));
  addr->sadb_address_reserved = 0;
  memset (addr + 1, '\0', sizeof (struct sockaddr_in));
  ((struct sockaddr_in *)(addr + 1))->sin_len = sizeof (struct sockaddr_in);
  ((struct sockaddr_in *)(addr + 1))->sin_family = AF_INET;
  ((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr = raddr;
  ((struct sockaddr_in *)(addr + 1))->sin_port = IPSEC_PORT_ANY;
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)addr,
			 PF_KEY_V2_NODE_MALLOCED) == -1)
    goto cleanup;
  addr = 0;

  /* Setup the POLICY extension.  */
  policy = (struct sadb_x_policy *)policy_buf;
  policy->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
  policy->sadb_x_policy_len = sizeof policy_buf / PF_KEY_V2_CHUNK;
  policy->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
  policy->sadb_x_policy_dir = IPSEC_DIR_OUTBOUND;
  policy->sadb_x_policy_reserved = 0;

  /* Setup the IPSECREQUEST extension part.  */
  ipsecrequest = (struct sadb_x_ipsecrequest *)(policy + 1);
  ipsecrequest->sadb_x_ipsecrequest_len
    = sizeof *ipsecrequest + 2 * sizeof *saddr;
  switch (proto)
    {
    case IPSEC_PROTO_IPSEC_ESP:
      ipsecrequest->sadb_x_ipsecrequest_proto = IPPROTO_ESP;
      break;
    case IPSEC_PROTO_IPSEC_AH:
      ipsecrequest->sadb_x_ipsecrequest_proto = IPPROTO_AH;
      break;
    default:
      log_print ("pf_key_v2_flow: invalid proto %d", proto);
      goto cleanup;
    }
  ipsecrequest->sadb_x_ipsecrequest_mode = IPSEC_MODE_TUNNEL;	/* XXX */
  ipsecrequest->sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE;
  ipsecrequest->sadb_x_ipsecrequest_reqid = 0;	/* XXX */

  /* Add source and destination addresses.  XXX IPv4 dependent */
  saddr = (struct sockaddr_in *)(ipsecrequest + 1);
  memset (saddr, '\0', sizeof *saddr);
  saddr->sin_len = sizeof (struct sockaddr_in);
  saddr->sin_family = AF_INET;
  saddr->sin_addr.s_addr = src;
  saddr->sin_port = 0;

  saddr++;
  memset (saddr, '\0', sizeof *saddr);
  saddr->sin_len = sizeof (struct sockaddr_in);
  saddr->sin_family = AF_INET;
  saddr->sin_addr.s_addr = dst;
  saddr->sin_port = 0;
  
  if (pf_key_v2_msg_add (flow, (struct sadb_ext *)policy, 0) == -1)
    goto cleanup;

  LOG_DBG ((LOG_SYSDEP, 50, "pf_key_v2_flow: src %x %x dst %x %x",
	    ntohl (laddr), ntohl (lmask), ntohl (raddr), ntohl (rmask)));

  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)
    {
      log_print ("pf_key_v2_flow: SPD%s: %s", delete ? "DELETE" : "ADD",
		 strerror (err));
      goto cleanup;
    }
  pf_key_v2_msg_free (ret);

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

  return 0;

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

#else
  log_error ("pf_key_v2_flow: not supported in pure PF_KEYv2");
  return -1;
#endif
}

/* Enable a flow given a SA.  */
int
pf_key_v2_enable_sa (struct sa *sa)
{
  struct ipsec_sa *isa = sa->data;
  struct sockaddr *dst, *src;
  int dstlen, srclen, error;
  struct proto *proto = TAILQ_FIRST (&sa->protos);
  in_addr_t hostmask = 0xffffffff; /* XXX IPv4 specific */

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

  /* XXX IPv4 specific */
  error = pf_key_v2_flow (isa->src_net, isa->src_mask, isa->dst_net,
			  isa->dst_mask, proto->spi[0], proto->proto,
			  ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			  ((struct sockaddr_in *)src)->sin_addr.s_addr, 0, 0);
  if (error)
    return error;

  /* Ingress flows */
  while (TAILQ_NEXT (proto, link))
    {
      error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      hostmask,
			      ((struct sockaddr_in *)src)->sin_addr.s_addr,
			      hostmask, proto->spi[1], proto->proto,
			      ((struct sockaddr_in *)src)->sin_addr.s_addr,
			      ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      0, 1);
      if (error)
	return error;
      proto = TAILQ_NEXT (proto, link);
    }

  /* The remote gateway is also allowed to talk to the subnet */
  error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
			  hostmask, isa->src_net, isa->src_mask,
			  proto->spi[1], proto->proto,
			  ((struct sockaddr_in *)src)->sin_addr.s_addr,
			  ((struct sockaddr_in *)dst)->sin_addr.s_addr, 0, 1);
  if (error)
    return error;

  /* The remote gateway is also allowed to talk to the local gateway */
  error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
			  hostmask,
			  ((struct sockaddr_in *)src)->sin_addr.s_addr,
			  hostmask, proto->spi[1], proto->proto,
			  ((struct sockaddr_in *)src)->sin_addr.s_addr,
			  ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			  0, 1);
  if (error)
    return error;

  return pf_key_v2_flow (isa->dst_net, isa->dst_mask, isa->src_net,
			 isa->src_mask, proto->spi[1], proto->proto,
			 ((struct sockaddr_in *)src)->sin_addr.s_addr,
			 ((struct sockaddr_in *)dst)->sin_addr.s_addr, 0, 1);
}

/* Disable a flow given a SA.  */
static int
pf_key_v2_disable_sa (struct sa *sa, int incoming)
{
  struct ipsec_sa *isa = sa->data;
  struct sockaddr *dst, *src;
  int dstlen, srclen, error;
  struct proto *proto = TAILQ_FIRST (&sa->protos);
  in_addr_t hostmask = 0xffffffff; /* XXX IPv4 specific */

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

  if (!incoming)
    return pf_key_v2_flow (isa->src_net, isa->src_mask, isa->dst_net,
			   isa->dst_mask, proto->spi[0], proto->proto,
			   ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			   ((struct sockaddr_in *)src)->sin_addr.s_addr, 1, 0);

  else
    {
      /* Ingress flow */
      while (TAILQ_NEXT (proto, link))
	{
          error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
				  hostmask,
				  ((struct sockaddr_in *)src)->sin_addr.s_addr,
				  hostmask, proto->spi[1], proto->proto,
				  ((struct sockaddr_in *)src)->sin_addr.s_addr,
				  ((struct sockaddr_in *)dst)->sin_addr.s_addr,
				  1, 1);
          if (error)
	    return error;
          proto = TAILQ_NEXT (proto, link);
	}

      error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      hostmask,
			      ((struct sockaddr_in *)src)->sin_addr.s_addr,
			      hostmask, proto->spi[1], proto->proto,
			      ((struct sockaddr_in *)src)->sin_addr.s_addr,
			      ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      1, 1);
      if (error)
	return error;

      error = pf_key_v2_flow (((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      hostmask, isa->src_net, isa->src_mask,
			      proto->spi[1], proto->proto,
			      ((struct sockaddr_in *)src)->sin_addr.s_addr,
			      ((struct sockaddr_in *)dst)->sin_addr.s_addr,
			      1, 1);
      if (error)
	return error;

      return pf_key_v2_flow (isa->dst_net, isa->dst_mask, isa->src_net,
			     isa->src_mask, proto->spi[1], proto->proto,
			     ((struct sockaddr_in *)src)->sin_addr.s_addr,
			     ((struct sockaddr_in *)dst)->sin_addr.s_addr,
                             1, 1);
    }
}

/*
 * 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 saddrlen, len, err;
  struct pf_key_v2_msg *delete = 0, *ret = 0;
#ifdef KAME
  struct sadb_x_sa2 ssa2;
#endif

  /*
   * If the SA was outbound and it has not yet been replaced, or it's
   * incoming, remove the flow associated with it.
   * We ignore any errors from the disabling of the flow, it does not matter.
   * Only remove flows if it was not an SA acquired by the kernel.
   */
  if (((!incoming && !(sa->flags & SA_FLAG_REPLACED)) || incoming)
      && !(sa->flags & SA_FLAG_ONDEMAND))
    pf_key_v2_disable_sa (sa, incoming);

  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;
    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;

#ifdef KAME
  memset(&ssa2, 0, sizeof ssa2);
  ssa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
  ssa2.sadb_x_sa2_len = sizeof ssa2 / PF_KEY_V2_CHUNK;
  ssa2.sadb_x_sa2_mode = 0;
  if (pf_key_v2_msg_add (delete, (struct sadb_ext *)&ssa2, 0) == -1)
    goto cleanup;
#endif

  /*
   * Setup the ADDRESS extensions.
   *
   * XXX Addresses have to be thought through.  Assumes IPv4.
   */
  if (incoming)
    sa->transport->vtbl->get_dst (sa->transport, &saddr, &saddrlen);
  else
    sa->transport->vtbl->get_src (sa->transport, &saddr, &saddrlen);
  len = sizeof *addr + PF_KEY_V2_ROUND (saddrlen);
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, saddr, saddrlen);
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  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, &saddrlen);
  else
    sa->transport->vtbl->get_dst (sa->transport, &saddr, &saddrlen);
  len = sizeof *addr + PF_KEY_V2_ROUND (saddrlen);
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, saddr, saddrlen);
  ((struct sockaddr_in *)(addr + 1))->sin_port = 0;
  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:
  if (addr)
    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;
}

/* 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);
    }
  else
    LOG_DBG ((LOG_SYSDEP, 70, "pf_key_v2_connection_check: SA for %s exists",
	      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;
  struct sa *sa;
  struct pf_key_v2_node *lifenode, *ext;

  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;

  /* XXX IPv4 specific.  */
  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",
	    inet_ntoa (((struct sockaddr_in *)dstaddr)->sin_addr),
	    ntohl (ssa->sadb_sa_spi), msg->sadb_msg_satype));

  /*
   * 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.
   *
   * XXX When anything else than AH and ESP is supported this needs to change.
   * XXX IPv4 specific.
   */
  sa = ipsec_sa_lookup (((struct sockaddr_in *)dstaddr)->sin_addr.s_addr,
			ssa->sadb_sa_spi,
			msg->sadb_msg_satype == SADB_SATYPE_ESP
			? IPSEC_PROTO_IPSEC_ESP : IPSEC_PROTO_IPSEC_AH);

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

  /*
   * If we want this connection to stay "forever", we should renegotiate
   * already at the soft expire, and certainly at the hard expire if we
   * haven't started a negotiation by then.  However, do not renegotiate
   * if this SA is already obsoleted by another.
   */
  if ((sa->flags & (SA_FLAG_STAYALIVE | SA_FLAG_REPLACED))
      == SA_FLAG_STAYALIVE)
    exchange_establish (sa->name, 0, 0);

  if (life->sadb_lifetime_exttype == SADB_EXT_LIFETIME_HARD)
    {
      /* This SA is still referenced by the software timeout.
       * However, sa_free will clean up all timeouts and
       * decrement reference counters.  We need to reference it
       * so that sa_release() works.
       */
      sa_reference (sa);
      /* Remove the old SA, it isn't useful anymore.  */
      sa_free (sa);
    }
}

/* Handle a PF_KEY SA ACQUIRE message PMSG.  */
static void
pf_key_v2_acquire (struct pf_key_v2_msg *pmsg)
{
#if !defined (SADB_X_ASKPOLICY)
  return;
#else  
  struct sadb_msg *msg, askpolicy_msg;
  struct pf_key_v2_msg *askpolicy = 0, *ret = 0;
  struct sadb_policy policy;
  struct sadb_address *dst = 0, *src = 0;
  struct sockaddr *dstaddr, *srcaddr = 0;
  struct sadb_comb *scmb = 0;
  struct sadb_prop *sprp = 0;
  struct sadb_ident *srcident = 0, *dstident = 0;
  char dstbuf[ADDRESS_MAX], srcbuf[ADDRESS_MAX], *peer = 0, conn[22];
  char confname[120];
  char *srcid = 0, *dstid = 0, *prefstring = 0;
  int slen, af;
  struct sockaddr *smask, *sflow, *dmask, *dflow;
  struct sadb_protocol *sproto;
  char ssflow[ADDRESS_MAX], sdflow[ADDRESS_MAX];
  char sdmask[ADDRESS_MAX], ssmask[ADDRESS_MAX];
  char lname[90], dname[90], configname[30];
  int shostflag = 0, dhostflag = 0;
  struct pf_key_v2_node *ext;

  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");
      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_PROPOSAL);
  if (ext)
    {
      sprp = ext->seg;
      scmb = (struct sadb_comb *)(sprp + 1);
    }

  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_policy_exttype = SADB_X_EXT_POLICY;
  policy.sadb_policy_len = sizeof policy / PF_KEY_V2_CHUNK;
  policy.sadb_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;

  bzero (ssflow, sizeof ssflow);
  bzero (sdflow, sizeof sdflow);
  bzero (ssmask, sizeof ssmask);
  bzero (sdmask, sizeof sdmask);

  switch (sflow->sa_family)
    {
    case AF_INET:
      if (inet_ntop (AF_INET, &((struct sockaddr_in *)sflow)->sin_addr, ssflow,
		     ADDRESS_MAX) == NULL)
	{
	  log_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (inet_ntop (AF_INET, &((struct sockaddr_in *)dflow)->sin_addr, sdflow,
		     ADDRESS_MAX) == NULL)
	{
	  log_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (inet_ntop (AF_INET, &((struct sockaddr_in *)smask)->sin_addr, ssmask,
		     ADDRESS_MAX) == NULL)
	{
	  log_error ("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_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (((struct sockaddr_in *)smask)->sin_addr.s_addr == INADDR_BROADCAST)
	shostflag = 1;
      if (((struct sockaddr_in *)dmask)->sin_addr.s_addr == INADDR_BROADCAST)
	dhostflag = 1;
      break;

    case AF_INET6: 
      if (inet_ntop (AF_INET6, &((struct sockaddr_in6 *)sflow)->sin6_addr,
		     ssflow, ADDRESS_MAX) == NULL)
	{
	  log_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (inet_ntop (AF_INET6, &((struct sockaddr_in6 *)dflow)->sin6_addr,
		     sdflow, ADDRESS_MAX) == NULL)
	{
	  log_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (inet_ntop (AF_INET6, &((struct sockaddr_in6 *)smask)->sin6_addr,
		     ssmask, ADDRESS_MAX) == NULL)
	{
	  log_error ("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_error ("pf_key_v2_acquire: inet_ntop failed");
	  goto fail;
	}
      if (IN6_IS_ADDR_FULL (&((struct sockaddr_in6 *)smask)->sin6_addr))
	shostflag = 1;
      if (IN6_IS_ADDR_FULL (&((struct sockaddr_in6 *)dmask)->sin6_addr))
	dhostflag = 1;
      break;
    }

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

  switch (dstaddr->sa_family)
    {
    case AF_INET:
      if (inet_ntop (AF_INET, &((struct sockaddr_in *)dstaddr)->sin_addr,
		     dstbuf, ADDRESS_MAX) == NULL)
	{
	  log_error ("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_error ("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_error ("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_error ("pf_key_v2_acquire: inet_ntop failed");
	      goto fail;
	    }
	  break;
	}
    }

  /* Insert source ID */
  if (srcident)
    {
      /* Check for valid type */
      switch (srcident->sadb_ident_type)
        {
	case SADB_IDENTTYPE_PREFIX:
	  /* XXX Process the address */
	  break;

	case SADB_IDENTTYPE_FQDN:
	  prefstring = "FQDN";
	  /* Fall through */

	case SADB_IDENTTYPE_MBOX:
	  slen = (srcident->sadb_ident_len * sizeof (u_int64_t))
	    - sizeof (struct sadb_ident);
	  if (!prefstring)
	    prefstring = "USER_FQDN";

	  srcid = malloc (slen + strlen (prefstring) + 1 + strlen ("ID:/"));
	  if (!srcid)
	    {
	      log_error ("pf_key_v2_acquire: malloc (%d) failed",
			 slen + strlen (prefstring) + 1 + strlen ("ID:/"));
	      goto fail;
	    }

	  sprintf (srcid, "ID:%s/", prefstring);
	  strlcat (srcid + strlen ("ID:/") + strlen (prefstring),
		   (char *)(srcident + 1),
		   slen + strlen (prefstring) + 1 + strlen ("ID:/"));

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

	      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)
    {
      /* Check for valid type */
      switch (dstident->sadb_ident_type)
        {
	case SADB_IDENTTYPE_PREFIX:
	  /* XXX Process the address */
	  break;

	case SADB_IDENTTYPE_FQDN:
	  prefstring = "FQDN";
	  /* Fall through */

	case SADB_IDENTTYPE_MBOX:
	  slen = (dstident->sadb_ident_len * sizeof (u_int64_t))
	    - sizeof (struct sadb_ident);
	  if (!prefstring)
	    prefstring = "USER_FQDN";

	  dstid = malloc (slen + strlen (prefstring) + 1 + strlen ("ID:/"));
	  if (!dstid)
	    {
	      log_error ("pf_key_v2_acquire: malloc (%d) failed",
			 slen + strlen (prefstring) + 1 + strlen ("ID:/"));
	      goto fail;
	    }

	  sprintf (dstid, "ID:%s/", prefstring);
	  strlcat (dstid + strlen ("ID:/") + strlen (prefstring),
		   (char *) (dstident + 1),
		   slen + strlen (prefstring) + 1 + strlen ("ID:/"));

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

	      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++)
    {
      sprintf (conn, "Connection-%d", connection_seq);
      sprintf (configname, "Config-Phase2-%d", connection_seq);

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

  /*
   * Set the IPsec connection entry. In particular, the following fields:
   * - Phase
   * - ISAKMP-peer
   * - Local-ID/Remote-ID (if provided)
   *
   * Also set the following section:
   *    [Peer-dstaddr(/srcaddr)(-srcid)(/dstid)]
   * with these fields:
   * - Phase
   * - ID (if provided)
   * - Remote-ID (if provided)
   * - Listen-address (if provided)
   * - Address
   * - Configuration (if an entry "ISAKMP-configuration-dstaddr(/srcaddr)"
   *                  exists -- otherwise use the defaults)
   */

  peer = malloc (strlen (dstbuf) + strlen (srcbuf) +
                 (srcid ? strlen (srcid) : 0) +
                (dstid ? strlen (dstid) : 0) + strlen ("Peer-/-/") + 1);
  if (!peer)
    goto fail;

  /*
   * The various cases:
   * - Peer-dstaddr
   * - Peer-dstaddr/srcaddr
   * - Peer-dstaddr/srcaddr-srcid
   * - Peer-dstaddr/srcaddr-srcid/dstid
   * - Peer-dstaddr/srcaddr-/dstid
   * - Peer-dstaddr-srcid/dstid
   * - Peer-dstaddr-/dstid
   * - Peer-dstaddr-srcid
   */
  sprintf (peer, "Peer-%s%s%s%s%s%s%s", dstbuf, srcaddr ? "/" : "",
	   srcaddr ? srcbuf : "", srcid ? "-" : "", srcid ? srcid : "",
	   dstid ? (srcid ? "/" : "-/") : "", dstid ? dstid : "");

  /* Set the IPsec connection section */
  af = conf_begin ();
  if (conf_set (af, conn, "Phase", "2", 0, 0)
      || conf_set (af, conn, "Flags", "__ondemand", 0 ,0)
      || conf_set (af, conn, "ISAKMP-peer", peer, 0, 0))
    {
      conf_end (af, 0);
      goto fail;
    }

  /* Set Phase 2 IDs -- this is the Local-ID section */
  sprintf (lname, "Phase2-ID:%s/%s", ssflow, ssmask);
  if (conf_set (af, conn, "Local-ID", lname, 0, 0))
    {
      conf_end (af, 0);
      goto fail;
    }

  if (!conf_get_str (lname, "ID-type"))
    {
      if (shostflag)
        {
	  if (conf_set (af, lname, "ID-type", "IPV4_ADDR", 0, 0)
	      || conf_set (af, lname, "Address", ssflow, 0, 0))
	    {
	      conf_end (af, 0);
	      goto fail;
	    }
	}
      else
        {
	  if (conf_set (af, lname, "ID-type", "IPV4_SUBNET", 0, 0)
	      || conf_set (af, lname, "Network", ssflow, 0, 0)
	      || conf_set (af, lname, "Netmask", ssmask, 0, 0))
	    {
	      conf_end (af, 0);
	      goto fail;
	    }
	}
    }

  /* Set Remote-ID section */
  sprintf (dname, "Phase2-ID:%s/%s", sdflow, sdmask);
  if (conf_set (af, conn, "Remote-ID", dname, 0, 0))
    {
      conf_end (af, 0);
      goto fail;
    }

  if (!conf_get_str (dname, "ID-type"))
    {
      if (dhostflag)
        {
	  if (conf_set (af, dname, "ID-type", "IPV4_ADDR", 0, 0)
	      || conf_set (af, dname, "Address", sdflow, 0, 0))
	    {
	      conf_end (af, 0);
	      goto fail;
	    }
	}
      else
        {
	  if (conf_set (af, dname, "ID-type", "IPV4_SUBNET", 0, 0)
	      || conf_set (af, dname, "Network", sdflow, 0, 0)
	      || conf_set (af, dname, "Netmask", sdmask, 0, 0))
	    {
	      conf_end (af, 0);
	      goto fail;
	    }
	}
    }

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

  /* Phase 2 configuration */
  if (conf_set (af, conn, "Configuration", configname, 0, 0))
    {
      conf_end (af, 0);
      goto fail;
    }

  if (conf_set (af, configname, "Exchange_type", "Quick_mode", 0, 0)
      || conf_set (af, configname, "DOI", "IPSEC", 0, 0)
      || conf_set (af, configname, "Suites",
		   "QM-ESP-3DES-SHA-PFS-SUITE", 0, 0))
    {
      conf_end (af, 0);
      goto fail;
    }

  /* Set the ISAKMP-peer section */
  if (!conf_get_str (peer, "Phase"))
    {
      if (conf_set (af, peer, "Phase", "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;
	}

      sprintf (confname, "ISAKMP-Configuration-%s", peer);
      if (conf_set (af, peer, "Configuration", confname, 0, 0))
        {
	  conf_end (af, 0);
	  goto fail;
	}

      /* XXX Default transform set should be settable */
      /* Phase 1 configuration */
      if (!conf_get_str (confname, "Exchange_Type"))
        {
	  if (conf_set (af, confname, "exchange_type", "ID_PROT", 0, 0)
	      || conf_set (af, confname, "DOI", "IPSEC", 0, 0)
	      || conf_set (af, confname, "Transforms", "3DES-SHA-RSA_SIG", 0,
			   0))
	    {
	      conf_end (af, 0);
	      goto fail;
	    }
	}

      /* 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
    {
      /* Phase 1 tag exists, there's nothing more we need to do */
    }

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

  /* Let's rock */
  pf_key_v2_connection_check (conn);

  /*
   * XXX Need to implement cleanup of sections after SAs expire. In
   * particular, we need to expire the IPsec connection section; we
   * could keep the ISAKMP-peer, Local-ID/Remote-ID sections.
   */

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

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:
      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.  Should probably be moved to sysdep.c
 */
int
pf_key_v2_group_spis (struct sa *sa, struct proto *proto1,
		      struct proto *proto2, int incoming)
{
#ifdef SADB_X_GRPSPIS
  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 saddrlen, err;
  size_t len;
#ifdef KAME
  struct sadb_x_sa2 kamesa2;
#endif

  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;
    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;

#ifndef KAME
  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;
#else
  memset(&kamesa2, 0, sizeof kamesa2);
  kamesa2.sadb_x_sa2_exttype = SADB_X_EXT_SA2;
  kamesa2.sadb_x_sa2_len = sizeof kamesa2 / PF_KEY_V2_CHUNK;
  kamesa2.sadb_x_sa2_mode = 0;
  if (pf_key_v2_msg_add (grpspis, (struct sadb_ext *)&kamesa2, 0) == -1)
    goto cleanup;
#endif

  /*
   * Setup the ADDRESS extensions.
   *
   * XXX Addresses have to be thought through.  Assumes IPv4.
   */
  if (incoming)
    sa->transport->vtbl->get_src (sa->transport, &saddr, &saddrlen);
  else
    sa->transport->vtbl->get_dst (sa->transport, &saddr, &saddrlen);
  len = sizeof *addr + PF_KEY_V2_ROUND (saddrlen);
  addr = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, saddr, saddrlen);
  ((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 = malloc (len);
  if (!addr)
    goto cleanup;
  addr->sadb_address_exttype = SADB_X_EXT_DST2;
  addr->sadb_address_len = len / PF_KEY_V2_CHUNK;
#ifndef __OpenBSD__
  addr->sadb_address_proto = 0;
  addr->sadb_address_prefixlen = 0;
#endif
  addr->sadb_address_reserved = 0;
  memcpy (addr + 1, saddr, saddrlen);
  ((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 PROTOCOL extension.  */
  protocol.sadb_protocol_exttype = SADB_X_EXT_PROTOCOL;
  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;
    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:
  if (addr)
     free (addr);
  if (grpspis)
    pf_key_v2_msg_free (grpspis);
  if (ret)
    pf_key_v2_msg_free (ret);
  return -1;

#else
  log_error ("pf_key_v2_group_spis: not supported in pure PF_KEYv2");
  return -1;
#endif
}