Logo Search packages:      
Sourcecode: ipsec-tools version File versions

pfkey.c

/* $Id: pfkey.c,v 1.27.2.5 2005/04/21 08:57:17 monas Exp $ */

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

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef ENABLE_NATT
# ifdef __linux__
#  include <linux/udp.h>
# endif
# if defined(__NetBSD__) || defined(__FreeBSD__)
#  include <netinet/udp.h>
# endif
#endif

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

#include <net/route.h>
#include <net/pfkeyv2.h>

#include <netinet/in.h>
#ifndef HAVE_NETINET6_IPSEC
#include <netinet/ipsec.h>
#else
#include <netinet6/ipsec.h>
#endif

#include "libpfkey.h"

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

#include "schedule.h"
#include "localconf.h"
#include "remoteconf.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "isakmp_inf.h"
#include "ipsec_doi.h"
#include "oakley.h"
#include "pfkey.h"
#include "handler.h"
#include "policy.h"
#include "algorithm.h"
#include "sainfo.h"
#include "proposal.h"
#include "admin.h"
#include "strnames.h"
#include "backupsa.h"
#include "gcmalloc.h"
#include "nattraversal.h"
#include "crypto_openssl.h"
#include "grabmyaddr.h"

#if defined(SADB_X_EALG_RIJNDAELCBC) && !defined(SADB_X_EALG_AESCBC)
#define SADB_X_EALG_AESCBC  SADB_X_EALG_RIJNDAELCBC
#endif

/* prototype */
static u_int ipsecdoi2pfkey_aalg __P((u_int));
static u_int ipsecdoi2pfkey_ealg __P((u_int));
static u_int ipsecdoi2pfkey_calg __P((u_int));
static u_int ipsecdoi2pfkey_alg __P((u_int, u_int));
static u_int keylen_aalg __P((u_int));
static u_int keylen_ealg __P((u_int, int));

static int pk_recvgetspi __P((caddr_t *));
static int pk_recvupdate __P((caddr_t *));
static int pk_recvadd __P((caddr_t *));
static int pk_recvdelete __P((caddr_t *));
static int pk_recvacquire __P((caddr_t *));
static int pk_recvexpire __P((caddr_t *));
static int pk_recvflush __P((caddr_t *));
static int getsadbpolicy __P((caddr_t *, int *, int, struct ph2handle *));
static int pk_recvspdupdate __P((caddr_t *));
static int pk_recvspdadd __P((caddr_t *));
static int pk_recvspddelete __P((caddr_t *));
static int pk_recvspdexpire __P((caddr_t *));
static int pk_recvspdget __P((caddr_t *));
static int pk_recvspddump __P((caddr_t *));
static int pk_recvspdflush __P((caddr_t *));
static struct sadb_msg *pk_recv __P((int, int *));

static int (*pkrecvf[]) __P((caddr_t *)) = {
NULL,
pk_recvgetspi,
pk_recvupdate,
pk_recvadd,
pk_recvdelete,
NULL, /* SADB_GET */
pk_recvacquire,
NULL, /* SABD_REGISTER */
pk_recvexpire,
pk_recvflush,
NULL, /* SADB_DUMP */
NULL, /* SADB_X_PROMISC */
NULL, /* SADB_X_PCHANGE */
pk_recvspdupdate,
pk_recvspdadd,
pk_recvspddelete,
pk_recvspdget,
NULL, /* SADB_X_SPDACQUIRE */
pk_recvspddump,
pk_recvspdflush,
NULL, /* SADB_X_SPDSETIDX */
pk_recvspdexpire,
NULL, /* SADB_X_SPDDELETE2 */
NULL, /* SADB_X_NAT_T_NEW_MAPPING */
};

static int addnewsp __P((caddr_t *));

/* cope with old kame headers - ugly */
#ifndef SADB_X_AALG_MD5
#define SADB_X_AALG_MD5       SADB_AALG_MD5     
#endif
#ifndef SADB_X_AALG_SHA
#define SADB_X_AALG_SHA       SADB_AALG_SHA
#endif
#ifndef SADB_X_AALG_NULL
#define SADB_X_AALG_NULL      SADB_AALG_NULL
#endif

#ifndef SADB_X_EALG_BLOWFISHCBC
#define SADB_X_EALG_BLOWFISHCBC     SADB_EALG_BLOWFISHCBC
#endif
#ifndef SADB_X_EALG_CAST128CBC
#define SADB_X_EALG_CAST128CBC      SADB_EALG_CAST128CBC
#endif
#ifndef SADB_X_EALG_RC5CBC
#ifdef SADB_EALG_RC5CBC
#define SADB_X_EALG_RC5CBC    SADB_EALG_RC5CBC
#endif
#endif

/*
 * PF_KEY packet handler
 *    0: success
 *    -1: fail
 */
int
pfkey_handler()
{
      struct sadb_msg *msg;
      int len;
      caddr_t mhp[SADB_EXT_MAX + 1];
      int error = -1;

      /* receive pfkey message. */
      len = 0;
      msg = (struct sadb_msg *)pk_recv(lcconf->sock_pfkey, &len);
      if (msg == NULL) {
            if (len < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "failed to recv from pfkey (%s)\n",
                        strerror(errno));
                  goto end;
            } else {
                  /* short message - msg not ready */
                  return 0;
            }
      }

      plog(LLV_DEBUG, LOCATION, NULL, "get pfkey %s message\n",
            s_pfkey_type(msg->sadb_msg_type));
      plogdump(LLV_DEBUG2, msg, msg->sadb_msg_len << 3);

      /* validity check */
      if (msg->sadb_msg_errno) {
            int pri;

            /* when SPD is empty, treat the state as no error. */
            if (msg->sadb_msg_type == SADB_X_SPDDUMP &&
                msg->sadb_msg_errno == ENOENT)
                  pri = LLV_DEBUG;
            else
                  pri = LLV_ERROR;

            plog(pri, LOCATION, NULL,
                  "pfkey %s failed: %s\n",
                  s_pfkey_type(msg->sadb_msg_type),
                  strerror(msg->sadb_msg_errno));

            goto end;
      }

      /* check pfkey message. */
      if (pfkey_align(msg, mhp)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed pfkey align (%s)\n",
                  ipsec_strerror());
            goto end;
      }
      if (pfkey_check(mhp)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed pfkey check (%s)\n",
                  ipsec_strerror());
            goto end;
      }
      msg = (struct sadb_msg *)mhp[0];

      /* safety check */
      if (msg->sadb_msg_type >= ARRAYLEN(pkrecvf)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "unknown PF_KEY message type=%u\n",
                  msg->sadb_msg_type);
            goto end;
      }

      if (pkrecvf[msg->sadb_msg_type] == NULL) {
            plog(LLV_INFO, LOCATION, NULL,
                  "unsupported PF_KEY message %s\n",
                  s_pfkey_type(msg->sadb_msg_type));
            goto end;
      }

      if ((pkrecvf[msg->sadb_msg_type])(mhp) < 0)
            goto end;

      error = 0;
end:
      if (msg)
            racoon_free(msg);
      return(error);
}

/*
 * dump SADB
 */
vchar_t *
pfkey_dump_sadb(satype)
      int satype;
{
      int s = -1;
      vchar_t *buf = NULL;
      pid_t pid = getpid();
      struct sadb_msg *msg = NULL;
      size_t bl, ml;
      int len;

      if ((s = pfkey_open()) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed pfkey open: %s\n",
                  ipsec_strerror());
            return NULL;
      }

      plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_dump\n");
      if (pfkey_send_dump(s, satype) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed dump: %s\n", ipsec_strerror());
            goto fail;
      }

      while (1) {
            if (msg)
                  racoon_free(msg);
            msg = pk_recv(s, &len);
            if (msg == NULL) {
                  if (len < 0)
                        goto done;
                  else
                        continue;
            }

            if (msg->sadb_msg_type != SADB_DUMP || msg->sadb_msg_pid != pid)
                  continue;

            ml = msg->sadb_msg_len << 3;
            bl = buf ? buf->l : 0;
            buf = vrealloc(buf, bl + ml);
            if (buf == NULL) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "failed to reallocate buffer to dump.\n");
                  goto fail;
            }
            memcpy(buf->v + bl, msg, ml);

            if (msg->sadb_msg_seq == 0)
                  break;
      }
      goto done;

fail:
      if (buf)
            vfree(buf);
      buf = NULL;
done:
      if (msg)
            racoon_free(msg);
      if (s >= 0)
            close(s);
      return buf;
}

#ifdef ENABLE_ADMINPORT
/*
 * flush SADB
 */
void
pfkey_flush_sadb(proto)
      u_int proto;
{
      int satype;

      /* convert to SADB_SATYPE */
      if ((satype = admin2pfkey_proto(proto)) < 0)
            return;

      plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_flush\n");
      if (pfkey_send_flush(lcconf->sock_pfkey, satype) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed send flush (%s)\n", ipsec_strerror());
            return;
      }

      return;
}
#endif

/*
 * These are the SATYPEs that we manage.  We register to get
 * PF_KEY messages related to these SATYPEs, and we also use
 * this list to determine which SATYPEs to delete SAs for when
 * we receive an INITIAL-CONTACT.
 */
const struct pfkey_satype pfkey_satypes[] = {
      { SADB_SATYPE_AH, "AH" },
      { SADB_SATYPE_ESP,      "ESP" },
      { SADB_X_SATYPE_IPCOMP, "IPCOMP" },
};
const int pfkey_nsatypes =
    sizeof(pfkey_satypes) / sizeof(pfkey_satypes[0]);

/*
 * PF_KEY initialization
 */
int
pfkey_init()
{
      int i, reg_fail;

      if ((lcconf->sock_pfkey = pfkey_open()) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed pfkey open (%s)\n", ipsec_strerror());
            return -1;
      }

      for (i = 0, reg_fail = 0; i < pfkey_nsatypes; i++) {
            plog(LLV_DEBUG, LOCATION, NULL,
                "call pfkey_send_register for %s\n",
                pfkey_satypes[i].ps_name);
            if (pfkey_send_register(lcconf->sock_pfkey,
                              pfkey_satypes[i].ps_satype) < 0 ||
                pfkey_recv_register(lcconf->sock_pfkey) < 0) {
                  plog(LLV_WARNING, LOCATION, NULL,
                      "failed to register %s (%s)\n",
                      pfkey_satypes[i].ps_name,
                      ipsec_strerror());
                  reg_fail++;
            }
      }

      if (reg_fail == pfkey_nsatypes) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to regist any protocol.\n");
            pfkey_close(lcconf->sock_pfkey);
            return -1;
      }

      initsp();

      if (pfkey_send_spddump(lcconf->sock_pfkey) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec sending spddump failed: %s\n",
                  ipsec_strerror());
            pfkey_close(lcconf->sock_pfkey);
            return -1;
      }
#if 0
      if (pfkey_promisc_toggle(1) < 0) {
            pfkey_close(lcconf->sock_pfkey);
            return -1;
      }
#endif
      return 0;
}

/* %%% for conversion */
/* IPSECDOI_ATTR_AUTH -> SADB_AALG */
static u_int
ipsecdoi2pfkey_aalg(hashtype)
      u_int hashtype;
{
      switch (hashtype) {
      case IPSECDOI_ATTR_AUTH_HMAC_MD5:
            return SADB_AALG_MD5HMAC;
      case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
            return SADB_AALG_SHA1HMAC;
      case IPSECDOI_ATTR_AUTH_KPDK:       /* need special care */
            return SADB_AALG_NONE;

      /* not supported */
      case IPSECDOI_ATTR_AUTH_DES_MAC:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Not supported hash type: %u\n", hashtype);
            return ~0;

      case 0: /* reserved */
      default:
            return SADB_AALG_NONE;

            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid hash type: %u\n", hashtype);
            return ~0;
      }
      /*NOTREACHED*/
}

/* IPSECDOI_ESP -> SADB_EALG */
static u_int
ipsecdoi2pfkey_ealg(t_id)
      u_int t_id;
{
      switch (t_id) {
      case IPSECDOI_ESP_DES_IV64:         /* sa_flags |= SADB_X_EXT_OLD */
            return SADB_EALG_DESCBC;
      case IPSECDOI_ESP_DES:
            return SADB_EALG_DESCBC;
      case IPSECDOI_ESP_3DES:
            return SADB_EALG_3DESCBC;
#ifdef SADB_X_EALG_RC5CBC
      case IPSECDOI_ESP_RC5:
            return SADB_X_EALG_RC5CBC;
#endif
      case IPSECDOI_ESP_CAST:
            return SADB_X_EALG_CAST128CBC;
      case IPSECDOI_ESP_BLOWFISH:
            return SADB_X_EALG_BLOWFISHCBC;
      case IPSECDOI_ESP_DES_IV32:   /* flags |= (SADB_X_EXT_OLD|
                                          SADB_X_EXT_IV4B)*/
            return SADB_EALG_DESCBC;
      case IPSECDOI_ESP_NULL:
            return SADB_EALG_NULL;
#ifdef SADB_X_EALG_AESCBC
      case IPSECDOI_ESP_AES:
            return SADB_X_EALG_AESCBC;
#endif
#ifdef SADB_X_EALG_TWOFISHCBC
      case IPSECDOI_ESP_TWOFISH:
            return SADB_X_EALG_TWOFISHCBC;
#endif

      /* not supported */
      case IPSECDOI_ESP_3IDEA:
      case IPSECDOI_ESP_IDEA:
      case IPSECDOI_ESP_RC4:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Not supported transform: %u\n", t_id);
            return ~0;

      case 0: /* reserved */
      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid transform id: %u\n", t_id);
            return ~0;
      }
      /*NOTREACHED*/
}

/* IPCOMP -> SADB_CALG */
static u_int
ipsecdoi2pfkey_calg(t_id)
      u_int t_id;
{
      switch (t_id) {
      case IPSECDOI_IPCOMP_OUI:
            return SADB_X_CALG_OUI;
      case IPSECDOI_IPCOMP_DEFLATE:
            return SADB_X_CALG_DEFLATE;
      case IPSECDOI_IPCOMP_LZS:
            return SADB_X_CALG_LZS;

      case 0: /* reserved */
      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid transform id: %u\n", t_id);
            return ~0;
      }
      /*NOTREACHED*/
}

/* IPSECDOI_PROTO -> SADB_SATYPE */
u_int
ipsecdoi2pfkey_proto(proto)
      u_int proto;
{
      switch (proto) {
      case IPSECDOI_PROTO_IPSEC_AH:
            return SADB_SATYPE_AH;
      case IPSECDOI_PROTO_IPSEC_ESP:
            return SADB_SATYPE_ESP;
      case IPSECDOI_PROTO_IPCOMP:
            return SADB_X_SATYPE_IPCOMP;

      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid ipsec_doi proto: %u\n", proto);
            return ~0;
      }
      /*NOTREACHED*/
}

static u_int
ipsecdoi2pfkey_alg(algclass, type)
      u_int algclass, type;
{
      switch (algclass) {
      case IPSECDOI_ATTR_AUTH:
            return ipsecdoi2pfkey_aalg(type);
      case IPSECDOI_PROTO_IPSEC_ESP:
            return ipsecdoi2pfkey_ealg(type);
      case IPSECDOI_PROTO_IPCOMP:
            return ipsecdoi2pfkey_calg(type);
      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid ipsec_doi algclass: %u\n", algclass);
            return ~0;
      }
      /*NOTREACHED*/
}

/* SADB_SATYPE -> IPSECDOI_PROTO */
u_int
pfkey2ipsecdoi_proto(satype)
      u_int satype;
{
      switch (satype) {
      case SADB_SATYPE_AH:
            return IPSECDOI_PROTO_IPSEC_AH;
      case SADB_SATYPE_ESP:
            return IPSECDOI_PROTO_IPSEC_ESP;
      case SADB_X_SATYPE_IPCOMP:
            return IPSECDOI_PROTO_IPCOMP;

      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "Invalid pfkey proto: %u\n", satype);
            return ~0;
      }
      /*NOTREACHED*/
}

/* IPSECDOI_ATTR_ENC_MODE -> IPSEC_MODE */
u_int
ipsecdoi2pfkey_mode(mode)
      u_int mode;
{
      switch (mode) {
      case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
#ifdef ENABLE_NATT
      case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_RFC:
      case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_DRAFT:
#endif
            return IPSEC_MODE_TUNNEL;
      case IPSECDOI_ATTR_ENC_MODE_TRNS:
#ifdef ENABLE_NATT
      case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC:
      case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT:
#endif
            return IPSEC_MODE_TRANSPORT;
      default:
            plog(LLV_ERROR, LOCATION, NULL, "Invalid mode type: %u\n", mode);
            return ~0;
      }
      /*NOTREACHED*/
}

/* IPSECDOI_ATTR_ENC_MODE -> IPSEC_MODE */
u_int
pfkey2ipsecdoi_mode(mode)
      u_int mode;
{
      switch (mode) {
      case IPSEC_MODE_TUNNEL:
            return IPSECDOI_ATTR_ENC_MODE_TUNNEL;
      case IPSEC_MODE_TRANSPORT:
            return IPSECDOI_ATTR_ENC_MODE_TRNS;
      case IPSEC_MODE_ANY:
            return IPSECDOI_ATTR_ENC_MODE_ANY;
      default:
            plog(LLV_ERROR, LOCATION, NULL, "Invalid mode type: %u\n", mode);
            return ~0;
      }
      /*NOTREACHED*/
}

/* default key length for encryption algorithm */
static u_int
keylen_aalg(hashtype)
      u_int hashtype;
{
      int res;

      if (hashtype == 0)
            return SADB_AALG_NONE;

      res = alg_ipsec_hmacdef_hashlen(hashtype);
      if (res == -1) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid hmac algorithm %u.\n", hashtype);
            return ~0;
      }
      return res;
}

/* default key length for encryption algorithm */
static u_int
keylen_ealg(enctype, encklen)
      u_int enctype;
      int encklen;
{
      int res;

      res = alg_ipsec_encdef_keylen(enctype, encklen);
      if (res == -1) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid encryption algorithm %u.\n", enctype);
            return ~0;
      }
      return res;
}

int
pfkey_convertfromipsecdoi(proto_id, t_id, hashtype,
            e_type, e_keylen, a_type, a_keylen, flags)
      u_int proto_id;
      u_int t_id;
      u_int hashtype;
      u_int *e_type;
      u_int *e_keylen;
      u_int *a_type;
      u_int *a_keylen;
      u_int *flags;
{
      *flags = 0;
      switch (proto_id) {
      case IPSECDOI_PROTO_IPSEC_ESP:
            if ((*e_type = ipsecdoi2pfkey_ealg(t_id)) == ~0)
                  goto bad;
            if ((*e_keylen = keylen_ealg(t_id, *e_keylen)) == ~0)
                  goto bad;
            *e_keylen >>= 3;

            if ((*a_type = ipsecdoi2pfkey_aalg(hashtype)) == ~0)
                  goto bad;
            if ((*a_keylen = keylen_aalg(hashtype)) == ~0)
                  goto bad;
            *a_keylen >>= 3;

            if (*e_type == SADB_EALG_NONE) {
                  plog(LLV_ERROR, LOCATION, NULL, "no ESP algorithm.\n");
                  goto bad;
            }
            break;

      case IPSECDOI_PROTO_IPSEC_AH:
            if ((*a_type = ipsecdoi2pfkey_aalg(hashtype)) == ~0)
                  goto bad;
            if ((*a_keylen = keylen_aalg(hashtype)) == ~0)
                  goto bad;
            *a_keylen >>= 3;

            if (t_id == IPSECDOI_ATTR_AUTH_HMAC_MD5 
             && hashtype == IPSECDOI_ATTR_AUTH_KPDK) {
                  /* AH_MD5 + Auth(KPDK) = RFC1826 keyed-MD5 */
                  *a_type = SADB_X_AALG_MD5;
                  *flags |= SADB_X_EXT_OLD;
            }
            *e_type = SADB_EALG_NONE;
            *e_keylen = 0;
            if (*a_type == SADB_AALG_NONE) {
                  plog(LLV_ERROR, LOCATION, NULL, "no AH algorithm.\n");
                  goto bad;
            }
            break;

      case IPSECDOI_PROTO_IPCOMP:
            if ((*e_type = ipsecdoi2pfkey_calg(t_id)) == ~0)
                  goto bad;
            *e_keylen = 0;

            *flags = SADB_X_EXT_RAWCPI;

            *a_type = SADB_AALG_NONE;
            *a_keylen = 0;
            if (*e_type == SADB_X_CALG_NONE) {
                  plog(LLV_ERROR, LOCATION, NULL, "no IPCOMP algorithm.\n");
                  goto bad;
            }
            break;

      default:
            plog(LLV_ERROR, LOCATION, NULL, "unknown IPsec protocol.\n");
            goto bad;
      }

      return 0;

    bad:
      errno = EINVAL;
      return -1;
}

/* called from scheduler */
void
pfkey_timeover_stub(p)
      void *p;
{

      pfkey_timeover((struct ph2handle *)p);
}

void
pfkey_timeover(iph2)
      struct ph2handle *iph2;
{
      plog(LLV_ERROR, LOCATION, NULL,
            "%s give up to get IPsec-SA due to time up to wait.\n",
            saddrwop2str(iph2->dst));
      SCHED_KILL(iph2->sce);

      /* If initiator side, send error to kernel by SADB_ACQUIRE. */
      if (iph2->side == INITIATOR)
            pk_sendeacquire(iph2);

      unbindph12(iph2);
      remph2(iph2);
      delph2(iph2);

      return;
}

/*%%%*/
/* send getspi message per ipsec protocol per remote address */
/*
 * the local address and remote address in ph1handle are dealed
 * with destination address and source address respectively.
 * Because SPI is decided by responder.
 */
int
pk_sendgetspi(iph2)
      struct ph2handle *iph2;
{
      struct sockaddr *src = NULL, *dst = NULL;
      u_int satype, mode;
      struct saprop *pp;
      struct saproto *pr;
      u_int32_t minspi, maxspi;
      int proxy = 0;

      if (iph2->side == INITIATOR) {
            pp = iph2->proposal;
            proxy = iph2->ph1->rmconf->support_proxy;
      } else {
            pp = iph2->approval;
            if (iph2->sainfo && iph2->sainfo->id_i)
                  proxy = 1;
      }

      /* for mobile IPv6 */
      if (proxy && iph2->src_id && iph2->dst_id &&
          ipsecdoi_transportmode(pp)) {
            src = iph2->src_id;
            dst = iph2->dst_id;
      } else {
            src = iph2->src;
            dst = iph2->dst;
      }

      for (pr = pp->head; pr != NULL; pr = pr->next) {

            /* validity check */
            satype = ipsecdoi2pfkey_proto(pr->proto_id);
            if (satype == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid proto_id %d\n", pr->proto_id);
                  return -1;
            }
            /* this works around a bug in Linux kernel where it allocates 4 byte
               spi's for IPCOMP */
            else if (satype == SADB_X_SATYPE_IPCOMP) {
                  minspi = ntohl (0x100);
                  maxspi = ntohl (0xffff);
            }
            else {
                  minspi = 0;
                  maxspi = 0;
            }
            mode = ipsecdoi2pfkey_mode(pr->encmode);
            if (mode == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid encmode %d\n", pr->encmode);
                  return -1;
            }

            plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_getspi\n");
            if (pfkey_send_getspi(
                        lcconf->sock_pfkey,
                        satype,
                        mode,
                        dst,              /* src of SA */
                        src,              /* dst of SA */
                        minspi, maxspi,
                        pr->reqid_in, iph2->seq) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "ipseclib failed send getspi (%s)\n",
                        ipsec_strerror());
                  return -1;
            }
            plog(LLV_DEBUG, LOCATION, NULL,
                  "pfkey GETSPI sent: %s\n",
                  sadbsecas2str(dst, src, satype, 0, mode));
      }

      return 0;
}

/*
 * receive GETSPI from kernel.
 */
static int
pk_recvgetspi(mhp) 
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_sa *sa;
      struct ph2handle *iph2;
      struct sockaddr *dst;
      int proto_id;
      int allspiok, notfound;
      struct saprop *pp;
      struct saproto *pr;

      /* validity check */
      if (mhp[SADB_EXT_SA] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb getspi message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
      dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]); /* note SA dir */

      /* the message has to be processed or not ? */
      if (msg->sadb_msg_pid != getpid()) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "%s message is not interesting "
                  "because pid %d is not mine.\n",
                  s_pfkey_type(msg->sadb_msg_type),
                  msg->sadb_msg_pid);
            return -1;
      }

      iph2 = getph2byseq(msg->sadb_msg_seq);
      if (iph2 == NULL) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "seq %d of %s message not interesting.\n",
                  msg->sadb_msg_seq,
                  s_pfkey_type(msg->sadb_msg_type));
            return -1;
      }

      if (iph2->status != PHASE2ST_GETSPISENT) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "status mismatch (db:%d msg:%d)\n",
                  iph2->status, PHASE2ST_GETSPISENT);
            return -1;
      }

      /* set SPI, and check to get all spi whether or not */
      allspiok = 1;
      notfound = 1;
      proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
      pp = iph2->side == INITIATOR ? iph2->proposal : iph2->approval;

      for (pr = pp->head; pr != NULL; pr = pr->next) {
            if (pr->proto_id == proto_id && pr->spi == 0) {
                  pr->spi = sa->sadb_sa_spi;
                  notfound = 0;
                  plog(LLV_DEBUG, LOCATION, NULL,
                        "pfkey GETSPI succeeded: %s\n",
                        sadbsecas2str(iph2->dst, iph2->src,
                            msg->sadb_msg_satype,
                            sa->sadb_sa_spi,
                            ipsecdoi2pfkey_mode(pr->encmode)));
            }
            if (pr->spi == 0)
                  allspiok = 0;     /* not get all spi */
      }

      if (notfound) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "get spi for unknown address %s\n",
                  saddrwop2str(iph2->dst));
            return -1;
      }

      if (allspiok) {
            /* update status */
            iph2->status = PHASE2ST_GETSPIDONE;
            if (isakmp_post_getspi(iph2) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "failed to start post getspi.\n");
                  unbindph12(iph2);
                  remph2(iph2);
                  delph2(iph2);
                  iph2 = NULL;
                  return -1;
            }
      }

      return 0;
}

/*
 * set inbound SA
 */
int
pk_sendupdate(iph2)
      struct ph2handle *iph2;
{
      struct saproto *pr;
      struct sockaddr *src = NULL, *dst = NULL;
      u_int e_type, e_keylen, a_type, a_keylen, flags;
      u_int satype, mode;
      u_int64_t lifebyte = 0;
      u_int wsize = 4;  /* XXX static size of window */ 
      int proxy = 0;
      struct ph2natt natt;

      /* sanity check */
      if (iph2->approval == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "no approvaled SAs found.\n");
      }

      if (iph2->side == INITIATOR)
            proxy = iph2->ph1->rmconf->support_proxy;
      else if (iph2->sainfo && iph2->sainfo->id_i)
            proxy = 1;

      /* for mobile IPv6 */
      if (proxy && iph2->src_id && iph2->dst_id &&
          ipsecdoi_transportmode(iph2->approval)) {
            src = iph2->src_id;
            dst = iph2->dst_id;
      } else {
            src = iph2->src;
            dst = iph2->dst;
      }

      for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
            /* validity check */
            satype = ipsecdoi2pfkey_proto(pr->proto_id);
            if (satype == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid proto_id %d\n", pr->proto_id);
                  return -1;
            }
            else if (satype == SADB_X_SATYPE_IPCOMP) {
                  /* IPCOMP has no replay window */
                  wsize = 0;
            }
#ifdef ENABLE_SAMODE_UNSPECIFIED
            mode = IPSEC_MODE_ANY;
#else
            mode = ipsecdoi2pfkey_mode(pr->encmode);
            if (mode == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid encmode %d\n", pr->encmode);
                  return -1;
            }
#endif

            /* set algorithm type and key length */
            e_keylen = pr->head->encklen;
            if (pfkey_convertfromipsecdoi(
                        pr->proto_id,
                        pr->head->trns_id,
                        pr->head->authtype,
                        &e_type, &e_keylen,
                        &a_type, &a_keylen, &flags) < 0)
                  return -1;

#if 0
            lifebyte = iph2->approval->lifebyte * 1024,
#else
            lifebyte = 0;
#endif

#ifdef ENABLE_NATT
            plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_update_nat\n");
            if (pr->udp_encap) {
                  memset (&natt, 0, sizeof (natt));
                  natt.type = iph2->ph1->natt_options->encaps_type;
                  natt.sport = extract_port (iph2->ph1->remote);
                  natt.dport = extract_port (iph2->ph1->local);
                  natt.oa = NULL;         // FIXME: Here comes OA!!!
            }
            else
                  memset (&natt, 0, sizeof (natt));

            if (pfkey_send_update_nat(
                        lcconf->sock_pfkey,
                        satype,
                        mode,
                        dst,
                        src,
                        pr->spi,
                        pr->reqid_in,
                        wsize,      
                        pr->keymat->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, lifebyte, iph2->approval->lifetime, 0,
                        iph2->seq,
                        natt.type, natt.sport, natt.dport, natt.oa) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "libipsec failed send update_nat (%s)\n",
                        ipsec_strerror());
                  return -1;
            }
#else
            plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_update\n");
            if (pfkey_send_update(
                        lcconf->sock_pfkey,
                        satype,
                        mode,
                        dst,
                        src,
                        pr->spi,
                        pr->reqid_in,
                        wsize,      
                        pr->keymat->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, lifebyte, iph2->approval->lifetime, 0,
                        iph2->seq) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "libipsec failed send update (%s)\n",
                        ipsec_strerror());
                  return -1;
            }
#endif /* ENABLE_NATT */

            if (!lcconf->pathinfo[LC_PATHTYPE_BACKUPSA])
                  continue;

            /*
             * It maybe good idea to call backupsa_to_file() after
             * racoon will receive the sadb_update messages.
             * But it is impossible because there is not key in the
             * information from the kernel.
             */
            if (backupsa_to_file(satype, mode, dst, src,
                        pr->spi, pr->reqid_in, 4,
                        pr->keymat->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, iph2->approval->lifebyte * 1024,
                        iph2->approval->lifetime, 0,
                        iph2->seq) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "backuped SA failed: %s\n",
                        sadbsecas2str(dst, src,
                        satype, pr->spi, mode));
            }
            plog(LLV_DEBUG, LOCATION, NULL,
                  "backuped SA: %s\n",
                  sadbsecas2str(dst, src,
                  satype, pr->spi, mode));
      }

      return 0;
}

static int
pk_recvupdate(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_sa *sa;
      struct sockaddr *src, *dst;
      struct ph2handle *iph2;
      u_int proto_id, encmode, sa_mode;
      int incomplete = 0;
      struct saproto *pr;

      /* ignore this message because of local test mode. */
      if (f_local)
            return 0;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_SA] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb update message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
      dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);
      sa = (struct sadb_sa *)mhp[SADB_EXT_SA];

      sa_mode = mhp[SADB_X_EXT_SA2] == NULL
            ? IPSEC_MODE_ANY
            : ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

      /* the message has to be processed or not ? */
      if (msg->sadb_msg_pid != getpid()) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "%s message is not interesting "
                  "because pid %d is not mine.\n",
                  s_pfkey_type(msg->sadb_msg_type),
                  msg->sadb_msg_pid);
            return -1;
      }

      iph2 = getph2byseq(msg->sadb_msg_seq);
      if (iph2 == NULL) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "seq %d of %s message not interesting.\n",
                  msg->sadb_msg_seq,
                  s_pfkey_type(msg->sadb_msg_type));
            return -1;
      }

      if (iph2->status != PHASE2ST_ADDSA) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "status mismatch (db:%d msg:%d)\n",
                  iph2->status, PHASE2ST_ADDSA);
            return -1;
      }

      /* check to complete all keys ? */
      for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
            proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
            if (proto_id == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid proto_id %d\n", msg->sadb_msg_satype);
                  return -1;
            }
            encmode = pfkey2ipsecdoi_mode(sa_mode);
            if (encmode == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid encmode %d\n", sa_mode);
                  return -1;
            }

            if (pr->proto_id == proto_id
             && pr->spi == sa->sadb_sa_spi) {
                  pr->ok = 1;
                  plog(LLV_DEBUG, LOCATION, NULL,
                        "pfkey UPDATE succeeded: %s\n",
                        sadbsecas2str(iph2->dst, iph2->src,
                            msg->sadb_msg_satype,
                            sa->sadb_sa_spi,
                            sa_mode));

                  plog(LLV_INFO, LOCATION, NULL,
                        "IPsec-SA established: %s\n",
                        sadbsecas2str(iph2->dst, iph2->src,
                              msg->sadb_msg_satype, sa->sadb_sa_spi,
                              sa_mode));
            }

            if (pr->ok == 0)
                  incomplete = 1;
      }

      if (incomplete)
            return 0;

      /* turn off the timer for calling pfkey_timeover() */
      SCHED_KILL(iph2->sce);
      
      /* update status */
      iph2->status = PHASE2ST_ESTABLISHED;

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

      /* count up */
      iph2->ph1->ph2cnt++;

      /* turn off schedule */
      if (iph2->scr)
            SCHED_KILL(iph2->scr);

      /*
       * since we are going to reuse the phase2 handler, we need to
       * remain it and refresh all the references between ph1 and ph2 to use.
       */
      unbindph12(iph2);

      iph2->sce = sched_new(iph2->approval->lifetime,
          isakmp_ph2expire_stub, iph2);

      plog(LLV_DEBUG, LOCATION, NULL, "===\n");
      return 0;
}

/*
 * set outbound SA
 */
int
pk_sendadd(iph2)
      struct ph2handle *iph2;
{
      struct saproto *pr;
      struct sockaddr *src = NULL, *dst = NULL;
      u_int e_type, e_keylen, a_type, a_keylen, flags;
      u_int satype, mode;
      u_int64_t lifebyte = 0;
      u_int wsize = 4; /* XXX static size of window */ 
      int proxy = 0;
      struct ph2natt natt;

      /* sanity check */
      if (iph2->approval == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "no approvaled SAs found.\n");
      }

      if (iph2->side == INITIATOR)
            proxy = iph2->ph1->rmconf->support_proxy;
      else if (iph2->sainfo && iph2->sainfo->id_i)
            proxy = 1;

      /* for mobile IPv6 */
      if (proxy && iph2->src_id && iph2->dst_id &&
          ipsecdoi_transportmode(iph2->approval)) {
            src = iph2->src_id;
            dst = iph2->dst_id;
      } else {
            src = iph2->src;
            dst = iph2->dst;
      }

      for (pr = iph2->approval->head; pr != NULL; pr = pr->next) {
            /* validity check */
            satype = ipsecdoi2pfkey_proto(pr->proto_id);
            if (satype == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid proto_id %d\n", pr->proto_id);
                  return -1;
            }
            else if (satype == SADB_X_SATYPE_IPCOMP) {
                  /* no replay window for IPCOMP */
                  wsize = 0;
            }
#ifdef ENABLE_SAMODE_UNSPECIFIED
            mode = IPSEC_MODE_ANY;
#else
            mode = ipsecdoi2pfkey_mode(pr->encmode);
            if (mode == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid encmode %d\n", pr->encmode);
                  return -1;
            }
#endif

            /* set algorithm type and key length */
            e_keylen = pr->head->encklen;
            if (pfkey_convertfromipsecdoi(
                        pr->proto_id,
                        pr->head->trns_id,
                        pr->head->authtype,
                        &e_type, &e_keylen,
                        &a_type, &a_keylen, &flags) < 0)
                  return -1;

#if 0
            lifebyte = iph2->approval->lifebyte * 1024,
#else
            lifebyte = 0;
#endif

#ifdef ENABLE_NATT
            plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_add_nat\n");

            if (pr->udp_encap) {
                  memset (&natt, 0, sizeof (natt));
                  natt.type = UDP_ENCAP_ESPINUDP;
                  natt.sport = extract_port (iph2->ph1->local);
                  natt.dport = extract_port (iph2->ph1->remote);
                  natt.oa = NULL;         // FIXME: Here comes OA!!!
            }
            else
                  memset (&natt, 0, sizeof (natt));

            if (pfkey_send_add_nat(
                        lcconf->sock_pfkey,
                        satype,
                        mode,
                        src,
                        dst,
                        pr->spi_p,
                        pr->reqid_out,
                        wsize,      
                        pr->keymat_p->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, lifebyte, iph2->approval->lifetime, 0,
                        iph2->seq,
                        natt.type, natt.sport, natt.dport, natt.oa) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "libipsec failed send add_nat (%s)\n",
                        ipsec_strerror());
                  return -1;
            }
#else
            plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_add\n");

            if (pfkey_send_add(
                        lcconf->sock_pfkey,
                        satype,
                        mode,
                        src,
                        dst,
                        pr->spi_p,
                        pr->reqid_out,
                        wsize,
                        pr->keymat_p->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, lifebyte, iph2->approval->lifetime, 0,
                        iph2->seq) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "libipsec failed send add (%s)\n",
                        ipsec_strerror());
                  return -1;
            }
#endif /* ENABLE_NATT */

            if (!lcconf->pathinfo[LC_PATHTYPE_BACKUPSA])
                  continue;

            /*
             * It maybe good idea to call backupsa_to_file() after
             * racoon will receive the sadb_update messages.
             * But it is impossible because there is not key in the
             * information from the kernel.
             */
            if (backupsa_to_file(satype, mode, src, dst,
                        pr->spi_p, pr->reqid_out, 4,
                        pr->keymat_p->v,
                        e_type, e_keylen, a_type, a_keylen, flags,
                        0, iph2->approval->lifebyte * 1024,
                        iph2->approval->lifetime, 0,
                        iph2->seq) < 0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "backuped SA failed: %s\n",
                        sadbsecas2str(src, dst,
                        satype, pr->spi_p, mode));
            }
            plog(LLV_DEBUG, LOCATION, NULL,
                  "backuped SA: %s\n",
                  sadbsecas2str(src, dst,
                  satype, pr->spi_p, mode));
      }

      return 0;
}

static int
pk_recvadd(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_sa *sa;
      struct sockaddr *src, *dst;
      struct ph2handle *iph2;
      u_int sa_mode;

      /* ignore this message because of local test mode. */
      if (f_local)
            return 0;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_SA] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb add message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
      dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);
      sa = (struct sadb_sa *)mhp[SADB_EXT_SA];

      sa_mode = mhp[SADB_X_EXT_SA2] == NULL
            ? IPSEC_MODE_ANY
            : ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

      /* the message has to be processed or not ? */
      if (msg->sadb_msg_pid != getpid()) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "%s message is not interesting "
                  "because pid %d is not mine.\n",
                  s_pfkey_type(msg->sadb_msg_type),
                  msg->sadb_msg_pid);
            return -1;
      }

      iph2 = getph2byseq(msg->sadb_msg_seq);
      if (iph2 == NULL) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "seq %d of %s message not interesting.\n",
                  msg->sadb_msg_seq,
                  s_pfkey_type(msg->sadb_msg_type));
            return -1;
      }

      /*
       * NOTE don't update any status of phase2 handle
       * because they must be updated by SADB_UPDATE message
       */

      plog(LLV_INFO, LOCATION, NULL,
            "IPsec-SA established: %s\n",
            sadbsecas2str(iph2->src, iph2->dst,
                  msg->sadb_msg_satype, sa->sadb_sa_spi, sa_mode));

      plog(LLV_DEBUG, LOCATION, NULL, "===\n");
      return 0;
}

static int
pk_recvexpire(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_sa *sa;
      struct sockaddr *src, *dst;
      struct ph2handle *iph2;
      u_int proto_id, sa_mode;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_SA] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || (mhp[SADB_EXT_LIFETIME_HARD] != NULL
        && mhp[SADB_EXT_LIFETIME_SOFT] != NULL)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb expire message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
      src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
      dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

      sa_mode = mhp[SADB_X_EXT_SA2] == NULL
            ? IPSEC_MODE_ANY
            : ((struct sadb_x_sa2 *)mhp[SADB_X_EXT_SA2])->sadb_x_sa2_mode;

      proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
      if (proto_id == ~0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid proto_id %d\n", msg->sadb_msg_satype);
            return -1;
      }

      plog(LLV_INFO, LOCATION, NULL,
            "IPsec-SA expired: %s\n",
            sadbsecas2str(src, dst,
                  msg->sadb_msg_satype, sa->sadb_sa_spi, sa_mode));

      iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi);
      if (iph2 == NULL) {
            /*
             * Ignore it because two expire messages are come up.
             * phase2 handler has been deleted already when 2nd message
             * is received.
             */
            plog(LLV_DEBUG, LOCATION, NULL,
                  "no such a SA found: %s\n",
                  sadbsecas2str(src, dst,
                      msg->sadb_msg_satype, sa->sadb_sa_spi,
                      sa_mode));
            return 0;
      }
      if (iph2->status != PHASE2ST_ESTABLISHED) {
            /*
             * If the status is not equal to PHASE2ST_ESTABLISHED,
             * racoon ignores this expire message.  There are two reason.
             * One is that the phase 2 probably starts because there is
             * a potential that racoon receives the acquire message
             * without receiving a expire message.  Another is that racoon
             * may receive the multiple expire messages from the kernel.
             */
            plog(LLV_WARNING, LOCATION, NULL,
                  "the expire message is received "
                  "but the handler has not been established.\n");
            return 0;
      }

      /* turn off the timer for calling isakmp_ph2expire() */ 
      SCHED_KILL(iph2->sce);

      iph2->status = PHASE2ST_EXPIRED;

      /* INITIATOR, begin phase 2 exchange. */
      /* allocate buffer for status management of pfkey message */
      if (iph2->side == INITIATOR) {

            initph2(iph2);

            /* update status for re-use */
            iph2->status = PHASE2ST_STATUS2;

            /* start isakmp initiation by using ident exchange */
            if (isakmp_post_acquire(iph2) < 0) {
                  plog(LLV_ERROR, LOCATION, iph2->dst,
                        "failed to begin ipsec sa "
                        "re-negotication.\n");
                  unbindph12(iph2);
                  remph2(iph2);
                  delph2(iph2);
                  return -1;
            }

            return 0;
            /*NOTREACHED*/
      }

      /* If not received SADB_EXPIRE, INITIATOR delete ph2handle. */
      /* RESPONDER always delete ph2handle, keep silent.  RESPONDER doesn't
       * manage IPsec SA, so delete the list */
      unbindph12(iph2);
      remph2(iph2);
      delph2(iph2);

      return 0;
}

static int
pk_recvacquire(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_x_policy *xpl;
      struct secpolicy *sp_out = NULL, *sp_in = NULL;
#define MAXNESTEDSA     5     /* XXX */
      struct ph2handle *iph2[MAXNESTEDSA];
      int n;      /* # of phase 2 handler */

      /* ignore this message because of local test mode. */
      if (f_local)
            return 0;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb acquire message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

      /* ignore if type is not IPSEC_POLICY_IPSEC */
      if (xpl->sadb_x_policy_type != IPSEC_POLICY_IPSEC) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "ignore ACQUIRE message. type is not IPsec.\n");
            return 0;
      }

      /* ignore it if src is multicast address */
    {
      struct sockaddr *sa = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

      if ((sa->sa_family == AF_INET
        && IN_MULTICAST(ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr)))
#ifdef INET6
       || (sa->sa_family == AF_INET6
        && IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)sa)->sin6_addr))
#endif
      ) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "ignore due to multicast address: %s.\n",
                  saddrwop2str(sa));
            return 0;
      }
    }
      
      /* ignore, if we do not listen on source address */
      {
            /* reasons behind:
             * - if we'll contact peer from address we do not listen -
             *   we will be unable to complete negotiation;
             * - if we'll negotiate using address we're listening -
             *   remote peer will send packets to address different
             *   than one in the policy, so kernel will drop them;
             * => therefore this acquire is not for us! --Aidas
             */
            struct sockaddr *sa = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
            struct myaddrs *p;
            int do_listen = 0;
            for (p = lcconf->myaddrs; p; p = p->next) {
                  if (!cmpsaddrwop(p->addr, sa)) {
                        do_listen = 1;
                        break;
                  }
            }

            if (!do_listen) {
                  plog(LLV_DEBUG, LOCATION, NULL,
                        "ignore because do not listen on source address : %s.\n",
                        saddrwop2str(sa));
                  return 0;
            }
      }

      /*
       * If there is a phase 2 handler against the policy identifier in
       * the acquire message, and if
       *    1. its state is less than PHASE2ST_ESTABLISHED, then racoon
       *       should ignore such a acquire message because the phase 2
       *       is just negotiating.
       *    2. its state is equal to PHASE2ST_ESTABLISHED, then racoon
       *       has to prcesss such a acquire message because racoon may
       *       lost the expire message.
       */
      iph2[0] = getph2byspid(xpl->sadb_x_policy_id);
      if (iph2[0] != NULL) {
            if (iph2[0]->status < PHASE2ST_ESTABLISHED) {
                  plog(LLV_DEBUG, LOCATION, NULL,
                        "ignore the acquire because ph2 found\n");
                  return -1;
            }
            if (iph2[0]->status == PHASE2ST_EXPIRED)
                  iph2[0] = NULL;
            /*FALLTHROUGH*/
      }

      /* search for proper policyindex */
      sp_out = getspbyspid(xpl->sadb_x_policy_id);
      if (sp_out == NULL) {
            plog(LLV_ERROR, LOCATION, NULL, "no policy found: id:%d.\n",
                  xpl->sadb_x_policy_id);
            return -1;
      }
      plog(LLV_DEBUG, LOCATION, NULL,
            "suitable outbound SP found: %s.\n", spidx2str(&sp_out->spidx));

      /* get inbound policy */
    {
      struct policyindex spidx;

      spidx.dir = IPSEC_DIR_INBOUND;
      memcpy(&spidx.src, &sp_out->spidx.dst, sizeof(spidx.src));
      memcpy(&spidx.dst, &sp_out->spidx.src, sizeof(spidx.dst));
      spidx.prefs = sp_out->spidx.prefd;
      spidx.prefd = sp_out->spidx.prefs;
      spidx.ul_proto = sp_out->spidx.ul_proto;

      sp_in = getsp(&spidx);
      if (sp_in) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "suitable inbound SP found: %s.\n",
                  spidx2str(&sp_in->spidx));
      } else {
            plog(LLV_NOTIFY, LOCATION, NULL,
                  "no in-bound policy found: %s\n",
                  spidx2str(&spidx));
      }
    }

      memset(iph2, 0, MAXNESTEDSA);

      n = 0;

      /* allocate a phase 2 */
      iph2[n] = newph2();
      if (iph2[n] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to allocate phase2 entry.\n");
            return -1;
      }
      iph2[n]->side = INITIATOR;
      iph2[n]->spid = xpl->sadb_x_policy_id;
      iph2[n]->satype = msg->sadb_msg_satype;
      iph2[n]->seq = msg->sadb_msg_seq;
      iph2[n]->status = PHASE2ST_STATUS2;

      /* set end addresses of SA */
      iph2[n]->dst = dupsaddr(PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]));
      if (iph2[n]->dst == NULL) {
            delph2(iph2[n]);
            return -1;
      }
      iph2[n]->src = dupsaddr(PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]));
      if (iph2[n]->src == NULL) {
            delph2(iph2[n]);
            return -1;
      }

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

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

      idsrc = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.src,
                        sp_out->spidx.prefs, sp_out->spidx.ul_proto);
      if (idsrc == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to get ID for %s\n",
                  spidx2str(&sp_out->spidx));
            delph2(iph2[n]);
            return -1;
      }
      iddst = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.dst,
                        sp_out->spidx.prefd, sp_out->spidx.ul_proto);
      if (iddst == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to get ID for %s\n",
                  spidx2str(&sp_out->spidx));
            vfree(idsrc);
            delph2(iph2[n]);
            return -1;
      }
      iph2[n]->sainfo = getsainfo(idsrc, iddst, NULL);
      vfree(idsrc);
      vfree(iddst);
      if (iph2[n]->sainfo == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to get sainfo.\n");
            delph2(iph2[n]);
            return -1;
            /* XXX should use the algorithm list from register message */
      }
    }

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

      /* start isakmp initiation by using ident exchange */
      /* XXX should be looped if there are multiple phase 2 handler. */
      if (isakmp_post_acquire(iph2[n]) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to begin ipsec sa negotication.\n");
            goto err;
      }

      return 0;

err:
      while (n >= 0) {
            unbindph12(iph2[n]);
            remph2(iph2[n]);
            delph2(iph2[n]);
            iph2[n] = NULL;
            n--;
      }
      return -1;
}

static int
pk_recvdelete(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_sa *sa;
      struct sockaddr *src, *dst;
      struct ph2handle *iph2 = NULL;
      u_int proto_id;

      /* ignore this message because of local test mode. */
      if (f_local)
            return 0;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_SA] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb acquire message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];
      sa = (struct sadb_sa *)mhp[SADB_EXT_SA];
      src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]);
      dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]);

      /* the message has to be processed or not ? */
      if (msg->sadb_msg_pid == getpid()) {
            plog(LLV_DEBUG, LOCATION, NULL,
                  "%s message is not interesting "
                  "because the message was originated by me.\n",
                  s_pfkey_type(msg->sadb_msg_type));
            return -1;
      }

      proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype);
      if (proto_id == ~0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid proto_id %d\n", msg->sadb_msg_satype);
            return -1;
      }

      iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi);
      if (iph2 == NULL) {
            /* ignore */
            plog(LLV_ERROR, LOCATION, NULL,
                  "no iph2 found: %s\n",
                  sadbsecas2str(src, dst, msg->sadb_msg_satype,
                        sa->sadb_sa_spi, IPSEC_MODE_ANY));
            return 0;
      }

      plog(LLV_ERROR, LOCATION, NULL,
            "pfkey DELETE received: %s\n",
            sadbsecas2str(iph2->src, iph2->dst,
                  msg->sadb_msg_satype, sa->sadb_sa_spi, IPSEC_MODE_ANY));

      /* send delete information */
      if (iph2->status == PHASE2ST_ESTABLISHED)
            isakmp_info_send_d2(iph2);

      unbindph12(iph2);
      remph2(iph2);
      delph2(iph2);

      return 0;
}

static int
pk_recvflush(mhp)
      caddr_t *mhp;
{
      /* ignore this message because of local test mode. */
      if (f_local)
            return 0;

      /* sanity check */
      if (mhp[0] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb acquire message passed.\n");
            return -1;
      }

      flushph2();

      return 0;
}

static int
getsadbpolicy(policy0, policylen0, type, iph2)
      caddr_t *policy0;
      int *policylen0, type;
      struct ph2handle *iph2;
{
      struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
      struct sadb_x_policy *xpl;
      struct sadb_x_ipsecrequest *xisr;
      struct saproto *pr;
      caddr_t policy, p;
      int policylen;
      int xisrlen;
      u_int satype, mode;

      /* get policy buffer size */
      policylen = sizeof(struct sadb_x_policy);
      if (type != SADB_X_SPDDELETE) {
            for (pr = iph2->approval->head; pr; pr = pr->next) {
                  xisrlen = sizeof(*xisr);
                  if (pr->encmode == IPSECDOI_ATTR_ENC_MODE_TUNNEL) {
                        xisrlen += (sysdep_sa_len(iph2->src)
                                  + sysdep_sa_len(iph2->dst));
                  }

                  policylen += PFKEY_ALIGN8(xisrlen);
            }
      }

      /* make policy structure */
      policy = racoon_malloc(policylen);
      if (!policy) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "buffer allocation failed.\n");
            return -1;
      }

      xpl = (struct sadb_x_policy *)policy;
      xpl->sadb_x_policy_len = PFKEY_UNIT64(policylen);
      xpl->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
      xpl->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
      xpl->sadb_x_policy_dir = spidx->dir;
      xpl->sadb_x_policy_id = 0;
#ifdef HAVE_PFKEY_POLICY_PRIORITY
      xpl->sadb_x_policy_priority = PRIORITY_DEFAULT;
#endif

      /* no need to append policy information any more if type is SPDDELETE */
      if (type == SADB_X_SPDDELETE)
            goto end;

      xisr = (struct sadb_x_ipsecrequest *)(xpl + 1);

      for (pr = iph2->approval->head; pr; pr = pr->next) {

            satype = doi2ipproto(pr->proto_id);
            if (satype == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid proto_id %d\n", pr->proto_id);
                  goto err;
            }
            mode = ipsecdoi2pfkey_mode(pr->encmode);
            if (mode == ~0) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid encmode %d\n", pr->encmode);
                  goto err;
            }

            /* 
             * the policy level cannot be unique because the policy
             * is defined later than SA, so req_id cannot be bound to SA.
             */
            xisr->sadb_x_ipsecrequest_proto = satype;
            xisr->sadb_x_ipsecrequest_mode = mode;
            xisr->sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE;
            xisr->sadb_x_ipsecrequest_reqid = 0;
            p = (caddr_t)(xisr + 1);

            xisrlen = sizeof(*xisr);

            if (pr->encmode == IPSECDOI_ATTR_ENC_MODE_TUNNEL) {
                  int src_len, dst_len;

                  src_len = sysdep_sa_len(iph2->src);
                  dst_len = sysdep_sa_len(iph2->dst);
                  xisrlen += src_len + dst_len;

                  memcpy(p, iph2->src, src_len);
                  p += src_len;

                  memcpy(p, iph2->dst, dst_len);
                  p += dst_len;
            }

            xisr->sadb_x_ipsecrequest_len = PFKEY_ALIGN8(xisrlen);
      }

end:
      *policy0 = policy;
      *policylen0 = policylen;

      return 0;

err:
      if (policy)
            racoon_free(policy);

      return -1;
}

int
pk_sendspdupdate2(iph2)
      struct ph2handle *iph2;
{
      struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
      caddr_t policy = NULL;
      int policylen = 0;
      u_int64_t ltime, vtime;

      ltime = iph2->approval->lifetime;
      vtime = 0;

      if (getsadbpolicy(&policy, &policylen, SADB_X_SPDUPDATE, iph2)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "getting sadb policy failed.\n");
            return -1;
      }

      if (pfkey_send_spdupdate2(
                  lcconf->sock_pfkey,
                  (struct sockaddr *)&spidx->src,
                  spidx->prefs,
                  (struct sockaddr *)&spidx->dst,
                  spidx->prefd,
                  spidx->ul_proto,
                  ltime, vtime,
                  policy, policylen, 0) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed send spdupdate2 (%s)\n",
                  ipsec_strerror());
            goto end;
      }
      plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spdupdate2\n");

end:
      if (policy)
            racoon_free(policy);

      return 0;
}

static int
pk_recvspdupdate(mhp)
      caddr_t *mhp;
{
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;
      struct policyindex spidx;
      struct secpolicy *sp;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spdupdate message passed.\n");
            return -1;
      }
      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &spidx);
#endif

      sp = getsp(&spidx);
      if (sp == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "such policy does not already exist: %s\n",
                  spidx2str(&spidx));
      } else {
            remsp(sp);
            delsp(sp);
      }

      if (addnewsp(mhp) < 0)
            return -1;

      return 0;
}

/*
 * this function has to be used by responder side.
 */
int
pk_sendspdadd2(iph2)
      struct ph2handle *iph2;
{
      struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
      caddr_t policy = NULL;
      int policylen = 0;
      u_int64_t ltime, vtime;

      ltime = iph2->approval->lifetime;
      vtime = 0;

      if (getsadbpolicy(&policy, &policylen, SADB_X_SPDADD, iph2)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "getting sadb policy failed.\n");
            return -1;
      }

      if (pfkey_send_spdadd2(
                  lcconf->sock_pfkey,
                  (struct sockaddr *)&spidx->src,
                  spidx->prefs,
                  (struct sockaddr *)&spidx->dst,
                  spidx->prefd,
                  spidx->ul_proto,
                  ltime, vtime,
                  policy, policylen, 0) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed send spdadd2 (%s)\n",
                  ipsec_strerror());
            goto end;
      }
      plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spdadd2\n");

end:
      if (policy)
            racoon_free(policy);

      return 0;
}

static int
pk_recvspdadd(mhp)
      caddr_t *mhp;
{
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;
      struct policyindex spidx;
      struct secpolicy *sp;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spdadd message passed.\n");
            return -1;
      }
      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &spidx);
#endif

      sp = getsp(&spidx);
      if (sp != NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "such policy already exists. "
                  "anyway replace it: %s\n",
                  spidx2str(&spidx));
            remsp(sp);
            delsp(sp);
      }

      if (addnewsp(mhp) < 0)
            return -1;

      return 0;
}

/*
 * this function has to be used by responder side.
 */
int
pk_sendspddelete(iph2)
      struct ph2handle *iph2;
{
      struct policyindex *spidx = (struct policyindex *)iph2->spidx_gen;
      caddr_t policy = NULL;
      int policylen;

      if (getsadbpolicy(&policy, &policylen, SADB_X_SPDDELETE, iph2)) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "getting sadb policy failed.\n");
            return -1;
      }

      if (pfkey_send_spddelete(
                  lcconf->sock_pfkey,
                  (struct sockaddr *)&spidx->src,
                  spidx->prefs,
                  (struct sockaddr *)&spidx->dst,
                  spidx->prefd,
                  spidx->ul_proto,
                  policy, policylen, 0) < 0) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "libipsec failed send spddelete (%s)\n",
                  ipsec_strerror());
            goto end;
      }
      plog(LLV_DEBUG, LOCATION, NULL, "call pfkey_send_spddelete\n");

end:
      if (policy)
            racoon_free(policy);

      return 0;
}

static int
pk_recvspddelete(mhp)
      caddr_t *mhp;
{
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;
      struct policyindex spidx;
      struct secpolicy *sp;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spddelete message passed.\n");
            return -1;
      }
      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &spidx);
#endif

      sp = getsp(&spidx);
      if (sp == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "no policy found: %s\n",
                  spidx2str(&spidx));
            return -1;
      }

      remsp(sp);
      delsp(sp);

      return 0;
}

static int
pk_recvspdexpire(mhp)
      caddr_t *mhp;
{
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;
      struct policyindex spidx;
      struct secpolicy *sp;

      /* sanity check */
      if (mhp[0] == NULL
       || mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spdexpire message passed.\n");
            return -1;
      }
      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &spidx);
#endif

      sp = getsp(&spidx);
      if (sp == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "no policy found: %s\n",
                  spidx2str(&spidx));
            return -1;
      }

      remsp(sp);
      delsp(sp);

      return 0;
}

static int
pk_recvspdget(mhp)
      caddr_t *mhp;
{
      /* sanity check */
      if (mhp[0] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spdget message passed.\n");
            return -1;
      }

      return 0;
}

static int
pk_recvspddump(mhp)
      caddr_t *mhp;
{
      struct sadb_msg *msg;
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;
      struct policyindex spidx;
      struct secpolicy *sp;

      /* sanity check */
      if (mhp[0] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spddump message passed.\n");
            return -1;
      }
      msg = (struct sadb_msg *)mhp[0];

      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

      if (saddr == NULL || daddr == NULL || xpl == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spddump message passed.\n");
            return -1;
      }

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &spidx);
#endif

      sp = getsp(&spidx);
      if (sp != NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "such policy already exists. "
                  "anyway replace it: %s\n",
                  spidx2str(&spidx));
            remsp(sp);
            delsp(sp);
      }

      if (addnewsp(mhp) < 0)
            return -1;

      return 0;
}

static int
pk_recvspdflush(mhp)
      caddr_t *mhp;
{
      /* sanity check */
      if (mhp[0] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spdflush message passed.\n");
            return -1;
      }

      flushsp();

      return 0;
}

/*
 * send error against acquire message to kenrel.
 */
int
pk_sendeacquire(iph2)
      struct ph2handle *iph2;
{
      struct sadb_msg *newmsg;
      int len;

      len = sizeof(struct sadb_msg);
      newmsg = racoon_calloc(1, len);
      if (newmsg == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to get buffer to send acquire.\n");
            return -1;
      }

      memset(newmsg, 0, len);
      newmsg->sadb_msg_version = PF_KEY_V2;
      newmsg->sadb_msg_type = SADB_ACQUIRE;
      newmsg->sadb_msg_errno = ENOENT;    /* XXX */
      newmsg->sadb_msg_satype = iph2->satype;
      newmsg->sadb_msg_len = PFKEY_UNIT64(len);
      newmsg->sadb_msg_reserved = 0;
      newmsg->sadb_msg_seq = iph2->seq;
      newmsg->sadb_msg_pid = (u_int32_t)getpid();

      /* send message */
      len = pfkey_send(lcconf->sock_pfkey, newmsg, len);

      racoon_free(newmsg);

      return 0;
}

/*
 * check if the algorithm is supported or not.
 * OUT       0: ok
 *    -1: ng
 */
int
pk_checkalg(class, calg, keylen)
      int class, calg, keylen;
{
      int sup, error;
      u_int alg;
      struct sadb_alg alg0;

      switch (algclass2doi(class)) {
      case IPSECDOI_PROTO_IPSEC_ESP:
            sup = SADB_EXT_SUPPORTED_ENCRYPT;
            break;
      case IPSECDOI_ATTR_AUTH:
            sup = SADB_EXT_SUPPORTED_AUTH;
            break;
      case IPSECDOI_PROTO_IPCOMP:
            plog(LLV_DEBUG, LOCATION, NULL,
                  "compression algorithm can not be checked "
                  "because sadb message doesn't support it.\n");
            return 0;
      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid algorithm class.\n");
            return -1;
      }
      alg = ipsecdoi2pfkey_alg(algclass2doi(class), algtype2doi(class, calg));
      if (alg == ~0)
            return -1;

      if (keylen == 0) {
            if (ipsec_get_keylen(sup, alg, &alg0)) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "%s.\n", ipsec_strerror());
                  return -1;
            }
            keylen = alg0.sadb_alg_minbits;
      }

      error = ipsec_check_keylen(sup, alg, keylen);
      if (error)
            plog(LLV_ERROR, LOCATION, NULL,
                  "%s.\n", ipsec_strerror());

      return error;
}

/*
 * differences with pfkey_recv() in libipsec/pfkey.c:
 * - never performs busy wait loop.
 * - returns NULL and set *lenp to negative on fatal failures
 * - returns NULL and set *lenp to non-negative on non-fatal failures
 * - returns non-NULL on success
 */
static struct sadb_msg *
pk_recv(so, lenp)
      int so;
      int *lenp;
{
      struct sadb_msg buf, *newmsg;
      int reallen;

      *lenp = recv(so, (caddr_t)&buf, sizeof(buf), MSG_PEEK);
      if (*lenp < 0)
            return NULL;      /*fatal*/
      else if (*lenp < sizeof(buf))
            return NULL;

      reallen = PFKEY_UNUNIT64(buf.sadb_msg_len);
      if ((newmsg = racoon_calloc(1, reallen)) == NULL)
            return NULL;

      *lenp = recv(so, (caddr_t)newmsg, reallen, MSG_PEEK);
      if (*lenp < 0) {
            racoon_free(newmsg);
            return NULL;      /*fatal*/
      } else if (*lenp != reallen) {
            racoon_free(newmsg);
            return NULL;
      }

      *lenp = recv(so, (caddr_t)newmsg, reallen, 0);
      if (*lenp < 0) {
            racoon_free(newmsg);
            return NULL;      /*fatal*/
      } else if (*lenp != reallen) {
            racoon_free(newmsg);
            return NULL;
      }

      return newmsg;
}

/* see handler.h */
u_int32_t
pk_getseq()
{
      return eay_random();
}

static int
addnewsp(mhp)
      caddr_t *mhp;
{
      struct secpolicy *new;
      struct sadb_address *saddr, *daddr;
      struct sadb_x_policy *xpl;

      /* sanity check */
      if (mhp[SADB_EXT_ADDRESS_SRC] == NULL
       || mhp[SADB_EXT_ADDRESS_DST] == NULL
       || mhp[SADB_X_EXT_POLICY] == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "inappropriate sadb spd management message passed.\n");
            return -1;
      }

      saddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_SRC];
      daddr = (struct sadb_address *)mhp[SADB_EXT_ADDRESS_DST];
      xpl = (struct sadb_x_policy *)mhp[SADB_X_EXT_POLICY];

#ifdef __linux__
      /* bsd skips over per-socket policies because there will be no
       * src and dst extensions in spddump messages. On Linux the only
       * way to achieve the same is check for policy id.
       */
      if (xpl->sadb_x_policy_id % 8 >= 3) return 0;
#endif

      new = newsp();
      if (new == NULL) {
            plog(LLV_ERROR, LOCATION, NULL,
                  "failed to allocate buffer\n");
            return -1;
      }

      new->spidx.dir = xpl->sadb_x_policy_dir;
      new->id = xpl->sadb_x_policy_id;
      new->policy = xpl->sadb_x_policy_type;
      new->req = NULL;

      /* check policy */
      switch (xpl->sadb_x_policy_type) {
      case IPSEC_POLICY_DISCARD:
      case IPSEC_POLICY_NONE:
      case IPSEC_POLICY_ENTRUST:
      case IPSEC_POLICY_BYPASS:
            break;

      case IPSEC_POLICY_IPSEC:
          {
            int tlen;
            struct sadb_x_ipsecrequest *xisr;
            struct ipsecrequest **p_isr = &new->req;

            /* validity check */
            if (PFKEY_EXTLEN(xpl) < sizeof(*xpl)) {
                  plog(LLV_ERROR, LOCATION, NULL,
                        "invalid msg length.\n");
                  return -1;
            }

            tlen = PFKEY_EXTLEN(xpl) - sizeof(*xpl);
            xisr = (struct sadb_x_ipsecrequest *)(xpl + 1);

            while (tlen > 0) {

                  /* length check */
                  if (xisr->sadb_x_ipsecrequest_len < sizeof(*xisr)) {
                        plog(LLV_ERROR, LOCATION, NULL,
                              "invalid msg length.\n");
                        return -1;
                  }

                  /* allocate request buffer */
                  *p_isr = newipsecreq();
                  if (*p_isr == NULL) {
                        plog(LLV_ERROR, LOCATION, NULL,
                              "failed to get new ipsecreq.\n");
                        return -1;
                  }

                  /* set values */
                  (*p_isr)->next = NULL;

                  switch (xisr->sadb_x_ipsecrequest_proto) {
                  case IPPROTO_ESP:
                  case IPPROTO_AH:
                  case IPPROTO_IPCOMP:
                        break;
                  default:
                        plog(LLV_ERROR, LOCATION, NULL,
                              "invalid proto type: %u\n",
                              xisr->sadb_x_ipsecrequest_proto);
                        return -1;
                  }
                  (*p_isr)->saidx.proto = xisr->sadb_x_ipsecrequest_proto;

                  switch (xisr->sadb_x_ipsecrequest_mode) {
                  case IPSEC_MODE_TRANSPORT:
                  case IPSEC_MODE_TUNNEL:
                        break;
                  case IPSEC_MODE_ANY:
                  default:
                        plog(LLV_ERROR, LOCATION, NULL,
                              "invalid mode: %u\n",
                              xisr->sadb_x_ipsecrequest_mode);
                        return -1;
                  }
                  (*p_isr)->saidx.mode = xisr->sadb_x_ipsecrequest_mode;

                  switch (xisr->sadb_x_ipsecrequest_level) {
                  case IPSEC_LEVEL_DEFAULT:
                  case IPSEC_LEVEL_USE:
                  case IPSEC_LEVEL_REQUIRE:
                        break;
                  case IPSEC_LEVEL_UNIQUE:
                        (*p_isr)->saidx.reqid =
                              xisr->sadb_x_ipsecrequest_reqid;
                        break;

                  default:
                        plog(LLV_ERROR, LOCATION, NULL,
                              "invalid level: %u\n",
                              xisr->sadb_x_ipsecrequest_level);
                        return -1;
                  }
                  (*p_isr)->level = xisr->sadb_x_ipsecrequest_level;

                  /* set IP addresses if there */
                  if (xisr->sadb_x_ipsecrequest_len > sizeof(*xisr)) {
                        struct sockaddr *paddr;

                        paddr = (struct sockaddr *)(xisr + 1);
                        bcopy(paddr, &(*p_isr)->saidx.src,
                              sysdep_sa_len(paddr));

                        paddr = (struct sockaddr *)((caddr_t)paddr
                                          + sysdep_sa_len(paddr));
                        bcopy(paddr, &(*p_isr)->saidx.dst,
                              sysdep_sa_len(paddr));
                  }

                  (*p_isr)->sp = new;

                  /* initialization for the next. */
                  p_isr = &(*p_isr)->next;
                  tlen -= xisr->sadb_x_ipsecrequest_len;

                  /* validity check */
                  if (tlen < 0) {
                        plog(LLV_ERROR, LOCATION, NULL,
                              "becoming tlen < 0\n");
                  }

                  xisr = (struct sadb_x_ipsecrequest *)((caddr_t)xisr
                                   + xisr->sadb_x_ipsecrequest_len);
            }
          }
            break;
      default:
            plog(LLV_ERROR, LOCATION, NULL,
                  "invalid policy type.\n");
            return -1;
      }

#ifdef HAVE_PFKEY_POLICY_PRIORITY
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  xpl->sadb_x_policy_priority,
                  &new->spidx);
#else
      KEY_SETSECSPIDX(xpl->sadb_x_policy_dir,
                  saddr + 1,
                  daddr + 1,
                  saddr->sadb_address_prefixlen,
                  daddr->sadb_address_prefixlen,
                  saddr->sadb_address_proto,
                  &new->spidx);
#endif

      inssp(new);

      return 0;
}

/* proto/mode/src->dst spi */
const char *
sadbsecas2str(src, dst, proto, spi, mode)
      struct sockaddr *src, *dst;
      int proto;
      u_int32_t spi;
      int mode;
{
      static char buf[256];
      u_int doi_proto, doi_mode = 0;
      char *p;
      int blen, i;

      doi_proto = pfkey2ipsecdoi_proto(proto);
      if (doi_proto == ~0)
            return NULL;
      if (mode) {
            doi_mode = pfkey2ipsecdoi_mode(mode);
            if (doi_mode == ~0)
                  return NULL;
      }

      blen = sizeof(buf) - 1;
      p = buf;

      i = snprintf(p, blen, "%s%s%s ",
            s_ipsecdoi_proto(doi_proto),
            mode ? "/" : "",
            mode ? s_ipsecdoi_encmode(doi_mode) : "");
      if (i < 0 || i >= blen)
            return NULL;
      p += i;
      blen -= i;

      i = snprintf(p, blen, "%s->", saddrwop2str(src));
      if (i < 0 || i >= blen)
            return NULL;
      p += i;
      blen -= i;

      i = snprintf(p, blen, "%s ", saddrwop2str(dst));
      if (i < 0 || i >= blen)
            return NULL;
      p += i;
      blen -= i;

      if (spi) {
            snprintf(p, blen, "spi=%lu(0x%lx)", (unsigned long)ntohl(spi),
                (unsigned long)ntohl(spi));
      }

      return buf;
}

Generated by  Doxygen 1.6.0   Back to index