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

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

Revision 1.44, Tue Apr 24 07:27:37 2001 UTC (23 years, 1 month ago) by niklas
Branch: MAIN
CVS Tags: OPENBSD_2_9_BASE, OPENBSD_2_9
Changes since 1.43: +22 -20 lines

Correct SA refcounting.  Fixes a bug where isakmpd could die when a peer was
discovered to have rebooted, and old now invalid SAs had to be garbage-
collected.

/*	$OpenBSD: ipsec.c,v 1.44 2001/04/24 07:27:37 niklas Exp $	*/
/*	$EOM: ipsec.c,v 1.143 2000/12/11 23:57:42 niklas Exp $	*/

/*
 * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 2001 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/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

#include "sysdep.h"

#include "attribute.h"
#include "conf.h"
#include "constants.h"
#include "crypto.h"
#include "dh.h"
#include "doi.h"
#include "exchange.h"
#include "hash.h"
#include "ike_aggressive.h"
#include "ike_auth.h"
#include "ike_main_mode.h"
#include "ike_quick_mode.h"
#include "ipsec.h"
#include "ipsec_doi.h"
#include "isakmp.h"
#include "log.h"
#include "math_group.h"
#include "message.h"
#include "prf.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"
#include "util.h"

/* Backwards compatibility.  */
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif

/* The replay window size used for all IPSec protocols if not overridden.  */
#define DEFAULT_REPLAY_WINDOW 16

struct ipsec_decode_arg {
  struct message *msg;
  struct sa *sa;
  struct proto *proto;
};

/* These variables hold the contacted peers ADT state.  */
struct contact {
  struct sockaddr *addr;
  socklen_t len;
} *contacts = 0;
int contact_cnt = 0, contact_limit = 0;

static int addr_cmp (const void *, const void *);
static int ipsec_add_contact (struct message *msg);
static int ipsec_contacted (struct message *msg);
#ifdef USE_DEBUG
static int ipsec_debug_attribute (u_int16_t, u_int8_t *, u_int16_t, void *);
#endif
static void ipsec_delete_spi (struct sa *, struct proto *, int);
static u_int16_t *ipsec_exchange_script (u_int8_t);
static void ipsec_finalize_exchange (struct message *);
static void ipsec_free_exchange_data (void *);
static void ipsec_free_proto_data (void *);
static void ipsec_free_sa_data (void *);
static struct keystate *ipsec_get_keystate (struct message *);
static u_int8_t *ipsec_get_spi (size_t *, u_int8_t, struct message *);
static int ipsec_handle_leftover_payload (struct message *, u_int8_t,
					  struct payload *);
static int ipsec_informational_post_hook (struct message *);
static int ipsec_informational_pre_hook (struct message *);
static int ipsec_initiator (struct message *);
static void ipsec_proto_init (struct proto *, char *);
static int ipsec_responder (struct message *);
static void ipsec_setup_situation (u_int8_t *);
static void ipsec_set_network (u_int8_t *, u_int8_t *, struct ipsec_sa *);
static size_t ipsec_situation_size (void);
static u_int8_t ipsec_spi_size (u_int8_t);
static int ipsec_validate_attribute (u_int16_t, u_int8_t *, u_int16_t, void *);
static int ipsec_validate_exchange (u_int8_t);
static int ipsec_validate_id_information (u_int8_t, u_int8_t *, u_int8_t *,
					  size_t, struct exchange *);
static int ipsec_validate_key_information (u_int8_t *, size_t);
static int ipsec_validate_notification (u_int16_t);
static int ipsec_validate_proto (u_int8_t);
static int ipsec_validate_situation (u_int8_t *, size_t *);
static int ipsec_validate_transform_id (u_int8_t, u_int8_t);

static struct doi ipsec_doi = {
  { 0 }, IPSEC_DOI_IPSEC,
  sizeof (struct ipsec_exch), sizeof (struct ipsec_sa),
  sizeof (struct ipsec_proto),
#ifdef USE_DEBUG
  ipsec_debug_attribute,
#endif
  ipsec_delete_spi,
  ipsec_exchange_script,
  ipsec_finalize_exchange,
  ipsec_free_exchange_data,
  ipsec_free_proto_data,
  ipsec_free_sa_data,
  ipsec_get_keystate,
  ipsec_get_spi,
  ipsec_handle_leftover_payload,
  ipsec_informational_post_hook,
  ipsec_informational_pre_hook,
  ipsec_is_attribute_incompatible,
  ipsec_proto_init,
  ipsec_setup_situation,
  ipsec_situation_size,
  ipsec_spi_size,
  ipsec_validate_attribute,
  ipsec_validate_exchange,
  ipsec_validate_id_information,
  ipsec_validate_key_information,
  ipsec_validate_notification,
  ipsec_validate_proto,
  ipsec_validate_situation,
  ipsec_validate_transform_id,
  ipsec_initiator,
  ipsec_responder,
  ipsec_decode_ids
};

int16_t script_quick_mode[] = {
  ISAKMP_PAYLOAD_HASH,		/* Initiator -> responder.  */
  ISAKMP_PAYLOAD_SA,
  ISAKMP_PAYLOAD_NONCE,
  EXCHANGE_SCRIPT_SWITCH,
  ISAKMP_PAYLOAD_HASH,		/* Responder -> initiator.  */
  ISAKMP_PAYLOAD_SA,
  ISAKMP_PAYLOAD_NONCE,
  EXCHANGE_SCRIPT_SWITCH,
  ISAKMP_PAYLOAD_HASH,		/* Initiator -> responder.  */
  EXCHANGE_SCRIPT_END
};

int16_t script_new_group_mode[] = {
  ISAKMP_PAYLOAD_HASH,		/* Initiator -> responder.  */
  ISAKMP_PAYLOAD_SA,
  EXCHANGE_SCRIPT_SWITCH,
  ISAKMP_PAYLOAD_HASH,		/* Responder -> initiator.  */
  ISAKMP_PAYLOAD_SA,
  EXCHANGE_SCRIPT_END
};

struct dst_spi_proto_arg {
  in_addr_t dst;
  u_int32_t spi;
  u_int8_t proto;
};

/*
 * Check if SA matches what we are asking for through V_ARG.  It has to
 * be a finished phase 2 SA.
 * if "proto" arg is 0, match any proto
 */
static int
ipsec_sa_check (struct sa *sa, void *v_arg)
{
  struct dst_spi_proto_arg *arg = v_arg;
  struct proto *proto;
  struct sockaddr *dst, *src;
  int dstlen, srclen;
  int incoming;

  if (sa->phase != 2 || !(sa->flags & SA_FLAG_READY))
    return 0;

  sa->transport->vtbl->get_dst (sa->transport, &dst, &dstlen);
  if (((struct sockaddr_in *)dst)->sin_addr.s_addr == arg->dst)
    incoming = 0;
  else
    {
      sa->transport->vtbl->get_src (sa->transport, &src, &srclen);
      if (((struct sockaddr_in *)src)->sin_addr.s_addr == arg->dst)
	incoming = 1;
      else
	return 0;
    }

  for (proto = TAILQ_FIRST (&sa->protos); proto;
       proto = TAILQ_NEXT (proto, link))
    if ((arg->proto == 0 || proto->proto == arg->proto)
       && memcmp (proto->spi[incoming], &arg->spi, sizeof arg->spi) == 0)
      return 1;
  return 0;
}

/* Find an SA with a "name" of DST, SPI & PROTO.  */
struct sa *
ipsec_sa_lookup (in_addr_t dst, u_int32_t spi, u_int8_t proto)
{
  struct dst_spi_proto_arg arg = { dst, spi, proto };

  return sa_find (ipsec_sa_check, &arg);
}

/*
 * Check if SA matches the flow of another SA in V_ARG.  It has to
 * be a finished non-replaced phase 2 SA.
 * XXX At some point other selectors will matter here too.
 */
static int
ipsec_sa_check_flow (struct sa *sa, void *v_arg)
{
  struct sa *sa2 = v_arg;
  struct ipsec_sa *isa = sa->data, *isa2 = sa2->data;

  if (sa == sa2 || sa->phase != 2
      || (sa->flags & (SA_FLAG_READY | SA_FLAG_REPLACED)) != SA_FLAG_READY)
    return 0;

  return isa->src_net == isa2->src_net && isa->src_mask == isa2->src_mask
    && isa->dst_net == isa2->dst_net && isa->dst_mask == isa2->dst_mask
    && isa->tproto == isa2->tproto && isa->sport == isa2->sport
    && isa->dport == isa2->dport;
}

/*
 * Do IPSec DOI specific finalizations task for the exchange where MSG was
 * the final message.
 */
static void
ipsec_finalize_exchange (struct message *msg)
{
  struct sa *isakmp_sa = msg->isakmp_sa;
  struct ipsec_sa *isa;
  struct exchange *exchange = msg->exchange;
  struct ipsec_exch *ie = exchange->data;
  struct sa *sa = 0, *old_sa;
  struct proto *proto, *last_proto = 0;

  switch (exchange->phase)
    {
    case 1:
      switch (exchange->type)
	{
	case ISAKMP_EXCH_ID_PROT:
	case ISAKMP_EXCH_AGGRESSIVE:
	  isa = isakmp_sa->data;
	  isa->hash = ie->hash->type;
	  isa->prf_type = ie->prf_type;
	  isa->skeyid_len = ie->skeyid_len;
	  isa->skeyid_d = ie->skeyid_d;
	  isa->skeyid_a = ie->skeyid_a;
	  /* Prevents early free of SKEYID_*.  */
	  ie->skeyid_a = ie->skeyid_d = 0;

	  /* If a lifetime was negotiated setup the expiration timers.  */
	  if (isakmp_sa->seconds)
	    sa_setup_expirations (isakmp_sa);
	  break;
	}
      break;

    case 2:
      switch (exchange->type)
	{
	case IKE_EXCH_QUICK_MODE:
	  /*
	   * Tell the application(s) about the SPIs and key material.
	   */
	  for (sa = TAILQ_FIRST (&exchange->sa_list); sa;
	       sa = TAILQ_NEXT (sa, next))
	    {
	      for (proto = TAILQ_FIRST (&sa->protos), last_proto = 0; proto;
		   proto = TAILQ_NEXT (proto, link))
		{
		  if (sysdep_ipsec_set_spi (sa, proto, 0)
		      || (last_proto
			  && sysdep_ipsec_group_spis (sa, last_proto, proto,
						      0))
		      || sysdep_ipsec_set_spi (sa, proto, 1)
		      || (last_proto
			  && sysdep_ipsec_group_spis (sa, last_proto, proto,
						      1)))
		    /* XXX Tear down this exchange.  */
		    return;
		  last_proto = proto;
		}

	      isa = sa->data;

	      if (exchange->initiator)
		/* Initiator is source, responder is destination.  */
		ipsec_set_network (ie->id_ci, ie->id_cr, isa);
	      else
		/* Responder is source, initiator is destination.  */
		ipsec_set_network (ie->id_cr, ie->id_ci, isa);

	      LOG_DBG ((LOG_EXCHANGE, 50,
			"ipsec_finalize_exchange: "
			"src %x %x dst %x %x tproto %u sport %u dport %u",
			ntohl (isa->src_net), ntohl (isa->src_mask),
			ntohl (isa->dst_net), ntohl (isa->dst_mask),
			ntohs (isa->tproto), isa->sport, ntohs (isa->dport)));

	      /*
	       * If this is not an SA acquired by the kernel, it needs
	       * to have a SPD entry (a.k.a. flow) set up.
	       */
	      if (!(sa->flags & SA_FLAG_ONDEMAND)
		  && sysdep_ipsec_enable_sa (sa, isakmp_sa))
		/* XXX Tear down this exchange.  */
		return;

	      /* Mark elder SAs with the same flow information as replaced.  */
	      while ((old_sa = sa_find (ipsec_sa_check_flow, sa)) != 0)
		sa_mark_replaced (old_sa);
	    }
	  break;
	}
    }
}

/* Set the client addresses in ISA from SRC_ID and DST_ID.  */
static void
ipsec_set_network (u_int8_t *src_id, u_int8_t *dst_id, struct ipsec_sa *isa)
{
  int id;

  /* Set source address.  */
  id = GET_ISAKMP_ID_TYPE (src_id);
  switch (id)
    {
    case IPSEC_ID_IPV4_ADDR:
      memcpy (&isa->src_net, src_id + ISAKMP_ID_DATA_OFF, sizeof isa->src_net);
      isa->src_mask = htonl (0xffffffff);
      memcpy (&isa->tproto,
	      src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PROTO_OFF,
	      IPSEC_ID_PROTO_LEN);
      memcpy (&isa->sport, src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF,
	      IPSEC_ID_PORT_LEN);
      break;

    case IPSEC_ID_IPV4_ADDR_SUBNET:
      memcpy (&isa->src_net, src_id + ISAKMP_ID_DATA_OFF, sizeof isa->src_net);
      memcpy (&isa->src_mask,
	      src_id + ISAKMP_ID_DATA_OFF + sizeof isa->src_net,
	      sizeof isa->src_mask);
      memcpy (&isa->tproto,
	      src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PROTO_OFF,
	      IPSEC_ID_PROTO_LEN);
      memcpy (&isa->sport, src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF,
	      IPSEC_ID_PORT_LEN);
      break;
  }

  /* Set destination address.  */
  id = GET_ISAKMP_ID_TYPE (dst_id);
  switch (id)
    {
    case IPSEC_ID_IPV4_ADDR:
      memcpy (&isa->dst_net, dst_id + ISAKMP_ID_DATA_OFF, sizeof isa->dst_net);
      isa->dst_mask = htonl (0xffffffff);
      memcpy (&isa->tproto,
	      dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PROTO_OFF,
	      IPSEC_ID_PROTO_LEN);
      memcpy (&isa->dport, dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF,
	      IPSEC_ID_PORT_LEN);
      break;

    case IPSEC_ID_IPV4_ADDR_SUBNET:
      memcpy (&isa->dst_net, dst_id + ISAKMP_ID_DATA_OFF, sizeof isa->dst_net);
      memcpy (&isa->dst_mask,
	      dst_id + ISAKMP_ID_DATA_OFF + sizeof isa->dst_net,
	      sizeof isa->dst_mask);
      memcpy (&isa->tproto,
	      dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PROTO_OFF,
	      IPSEC_ID_PROTO_LEN);
      memcpy (&isa->dport, dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF,
	      IPSEC_ID_PORT_LEN);
      break;
    }
}

/* Free the DOI-specific exchange data pointed to by VIE.  */
static void
ipsec_free_exchange_data (void *vie)
{
  struct ipsec_exch *ie = vie;

  if (ie->sa_i_b)
    free (ie->sa_i_b);
  if (ie->id_ci)
    free (ie->id_ci);
  if (ie->id_cr)
    free (ie->id_cr);
  if (ie->g_xi)
    free (ie->g_xi);
  if (ie->g_xr)
    free (ie->g_xr);
  if (ie->g_xy)
    free (ie->g_xy);
  if (ie->skeyid)
    free (ie->skeyid);
  if (ie->skeyid_d)
    free (ie->skeyid_d);
  if (ie->skeyid_a)
    free (ie->skeyid_a);
  if (ie->skeyid_e)
    free (ie->skeyid_e);
  if (ie->hash_i)
    free (ie->hash_i);
  if (ie->hash_r)
    free (ie->hash_r);
  if (ie->group)
    group_free (ie->group);
}

/* Free the DOI-specific SA data pointed to by VISA.  */
static void
ipsec_free_sa_data (void *visa)
{
  struct ipsec_sa *isa = visa;

  if (isa->skeyid_a)
    free (isa->skeyid_a);
  if (isa->skeyid_d)
    free (isa->skeyid_d);
}

/* Free the DOI-specific protocol data of an SA pointed to by VIPROTO.  */
static void
ipsec_free_proto_data (void *viproto)
{
  struct ipsec_proto *iproto = viproto;
  int i;

  for (i = 0; i < 2; i++)
    if (iproto->keymat[i])
      free (iproto->keymat[i]);
}

/* Return exchange script based on TYPE.  */
static u_int16_t *
ipsec_exchange_script (u_int8_t type)
{
  switch (type)
    {
    case IKE_EXCH_QUICK_MODE:
      return script_quick_mode;
    case IKE_EXCH_NEW_GROUP_MODE:
      return script_new_group_mode;
    }
  return 0;
}

/* Initialize this DOI, requires doi_init to already have been called.  */
void
ipsec_init ()
{
  doi_register (&ipsec_doi);
}

/* Given a message MSG, return a suitable IV (or rather keystate).  */
static struct keystate *
ipsec_get_keystate (struct message *msg)
{
  struct keystate *ks;
  struct hash *hash;

  /* If we have already have an IV, use it.  */
  if (msg->exchange && msg->exchange->keystate)
    {
      ks = malloc (sizeof *ks);
      if (!ks)
	{
	  log_error ("ipsec_get_keystate: malloc (%d) failed", sizeof *ks);
	  return 0;
	}
      memcpy (ks, msg->exchange->keystate, sizeof *ks);
      return ks;
    }

  /*
   * For phase 2 when no SA yet is setup we need to hash the IV used by
   * the ISAKMP SA concatenated with the message ID, and use that as an
   * IV for further cryptographic operations.
   */
  if (!msg->isakmp_sa->keystate)
    {
      log_print ("ipsec_get_keystate: no keystate in ISAKMP SA %p",
		 msg->isakmp_sa);
      return 0;
    }
  ks = crypto_clone_keystate (msg->isakmp_sa->keystate);
  if (!ks)
    return 0;

  hash = hash_get (((struct ipsec_sa *)msg->isakmp_sa->data)->hash);
  hash->Init (hash->ctx);
  LOG_DBG_BUF ((LOG_CRYPTO, 80, "ipsec_get_keystate: final phase 1 IV",
		ks->riv, ks->xf->blocksize));
  hash->Update (hash->ctx, ks->riv, ks->xf->blocksize);
  LOG_DBG_BUF ((LOG_CRYPTO, 80, "ipsec_get_keystate: message ID",
		((u_int8_t *)msg->iov[0].iov_base)
		+ ISAKMP_HDR_MESSAGE_ID_OFF,
		ISAKMP_HDR_MESSAGE_ID_LEN));
  hash->Update (hash->ctx,
		((u_int8_t *)msg->iov[0].iov_base) + ISAKMP_HDR_MESSAGE_ID_OFF,
		ISAKMP_HDR_MESSAGE_ID_LEN);
  hash->Final (hash->digest, hash->ctx);
  crypto_init_iv (ks, hash->digest, ks->xf->blocksize);
  LOG_DBG_BUF ((LOG_CRYPTO, 80, "ipsec_get_keystate: phase 2 IV",
		hash->digest, ks->xf->blocksize));
  return ks;
}

static void
ipsec_setup_situation (u_int8_t *buf)
{
  SET_IPSEC_SIT_SIT (buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY);
}

static size_t
ipsec_situation_size (void)
{
  return IPSEC_SIT_SIT_LEN;
}

static u_int8_t
ipsec_spi_size (u_int8_t proto)
{
  return IPSEC_SPI_SIZE;
}

static int
ipsec_validate_attribute (u_int16_t type, u_int8_t *value, u_int16_t len,
			  void *vmsg)
{
  struct message *msg = vmsg;

  if ((msg->exchange->phase == 1
       && (type < IKE_ATTR_ENCRYPTION_ALGORITHM
	   || type > IKE_ATTR_GROUP_ORDER))
      || (msg->exchange->phase == 2
	  && (type < IPSEC_ATTR_SA_LIFE_TYPE
	      || type > IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM)))
    return -1;
  return 0;
}

static int
ipsec_validate_exchange (u_int8_t exch)
{
  return exch != IKE_EXCH_QUICK_MODE && exch != IKE_EXCH_NEW_GROUP_MODE;
}

static int
ipsec_validate_id_information (u_int8_t type, u_int8_t *extra, u_int8_t *buf,
			       size_t sz, struct exchange *exchange)
{
  u_int8_t proto = GET_IPSEC_ID_PROTO (extra);
  u_int16_t port = GET_IPSEC_ID_PORT (extra);

  LOG_DBG ((LOG_MESSAGE, 0,
	    "ipsec_validate_id_information: proto %d port %d type %d",
	    proto, port, type));
  if (type < IPSEC_ID_IPV4_ADDR || type > IPSEC_ID_KEY_ID)
    return -1;

  switch (type)
    {
    case IPSEC_ID_IPV4_ADDR:
      LOG_DBG_BUF ((LOG_MESSAGE, 40, "ipsec_validate_id_information: IPv4",
		    buf, 4));
      break;

    case IPSEC_ID_IPV4_ADDR_SUBNET:
      LOG_DBG_BUF ((LOG_MESSAGE, 40,
		    "ipsec_validate_id_information: IPv4 network/netmask",
		    buf, 8));
      break;

    default:
      break;
    }

  if (exchange->phase == 1
      && (proto != IPPROTO_UDP || port != UDP_DEFAULT_PORT)
      && (proto != 0 || port != 0))
    {
/* XXX SSH's ISAKMP tester fails this test (proto 17 - port 0).  */
#ifdef notyet
      return -1;
#else
      log_print ("ipsec_validate_id_information: "
		 "dubious ID information accepted");
#endif
    }

  /* XXX More checks?  */

  return 0;
}

static int
ipsec_validate_key_information (u_int8_t *buf, size_t sz)
{
  /* XXX Not implemented yet.  */
  return 0;
}

static int
ipsec_validate_notification (u_int16_t type)
{
  return type < IPSEC_NOTIFY_RESPONDER_LIFETIME
    || type > IPSEC_NOTIFY_INITIAL_CONTACT ? -1 : 0;
}

static int
ipsec_validate_proto (u_int8_t proto)
{
  return proto < IPSEC_PROTO_IPSEC_AH || proto > IPSEC_PROTO_IPCOMP ? -1 : 0;
}

static int
ipsec_validate_situation (u_int8_t *buf, size_t *sz)
{
  int sit = GET_IPSEC_SIT_SIT (buf);
  int off;

  if (sit & (IPSEC_SIT_SECRECY | IPSEC_SIT_INTEGRITY))
    {
      /*
       * XXX All the roundups below, round up to 32 bit boundaries given
       * that the situation field is aligned.  This is not necessarily so,
       * but I interpret the drafts as this is like this they want it.
       */
      off = ROUNDUP_32 (GET_IPSEC_SIT_SECRECY_LENGTH (buf));
      off += ROUNDUP_32 (GET_IPSEC_SIT_SECRECY_CAT_LENGTH (buf + off));
      off += ROUNDUP_32 (GET_IPSEC_SIT_INTEGRITY_LENGTH (buf + off));
      off += ROUNDUP_32 (GET_IPSEC_SIT_INTEGRITY_CAT_LENGTH (buf + off));
      *sz = off + IPSEC_SIT_SZ;
    }
  else
    *sz = IPSEC_SIT_SIT_LEN;

  /* Currently only "identity only" situations are supported.  */
#ifdef notdef
  return
    sit & ~(IPSEC_SIT_IDENTITY_ONLY | IPSEC_SIT_SECRECY | IPSEC_SIT_INTEGRITY);
#else
   return sit & ~IPSEC_SIT_IDENTITY_ONLY;
#endif
    return 1;
  return 0;
}

static int
ipsec_validate_transform_id (u_int8_t proto, u_int8_t transform_id)
{
  switch (proto)
    {
      /*
       * As no unexpected protocols can occur, we just tie the default case
       * to the first case, in orer to silence a GCC warning.
       */
    default:
    case ISAKMP_PROTO_ISAKMP:
      return transform_id != IPSEC_TRANSFORM_KEY_IKE;
    case IPSEC_PROTO_IPSEC_AH:
      return
	transform_id < IPSEC_AH_MD5 || transform_id > IPSEC_AH_DES ? -1 : 0;
    case IPSEC_PROTO_IPSEC_ESP:
      return transform_id < IPSEC_ESP_DES_IV64
	|| transform_id > IPSEC_ESP_AES ? -1 : 0;
    case IPSEC_PROTO_IPCOMP:
      return transform_id < IPSEC_IPCOMP_OUI
	|| transform_id > IPSEC_IPCOMP_V42BIS ? -1 : 0;
    }
}

static int
ipsec_initiator (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  int (**script) (struct message *msg) = 0;

  /* Check that the SA is coherent with the IKE rules.  */
  if ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT
       && exchange->type != ISAKMP_EXCH_AGGRESSIVE
       && exchange->type != ISAKMP_EXCH_INFO)
      || (exchange->phase == 2 && exchange->type != IKE_EXCH_QUICK_MODE
	  && exchange->type != ISAKMP_EXCH_INFO))
    {
      log_print ("ipsec_initiator: unsupported exchange type %d in phase %d",
		 exchange->type, exchange->phase);
      return -1;
    }

  switch (exchange->type)
    {
    case ISAKMP_EXCH_ID_PROT:
      script = ike_main_mode_initiator;
      break;
#ifdef USE_AGGRESSIVE
    case ISAKMP_EXCH_AGGRESSIVE:
      script = ike_aggressive_initiator;
      break;
#endif
    case ISAKMP_EXCH_INFO:
      return message_send_info (msg);
    case IKE_EXCH_QUICK_MODE:
      script = ike_quick_mode_initiator;
      break;
    default:
      log_print ("ipsec_initiator: unsupported exchange type %d",
		 exchange->type);
      return -1;
    }

  /* Run the script code for this step.  */
  if (script)
    return script[exchange->step] (msg);

  return 0;
}

/*
 * delete all SA's from addr with the associated proto and SPI's
 *
 * spis[] is an array of SPIs of size 16-octet for proto ISAKMP
 * or 4-octet otherwise.
 */
static void
ipsec_delete_spi_list (struct sockaddr *addr, u_int8_t proto, 
                       u_int8_t *spis, int nspis, char *type)
{
  u_int32_t iaddr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
  struct sa *sa;
  int i;

  for (i = 0; i < nspis; i++) 
    {
      if (proto == ISAKMP_PROTO_ISAKMP)
        {
          u_int8_t *spi = spis + i * ISAKMP_HDR_COOKIES_LEN;

          /* 
           * This really shouldn't happen in IPSEC DOI
           * code, but Cisco VPN 3000 sends ISAKMP DELETE's
           * this way.
           */
          sa = sa_lookup_isakmp_sa (addr, spi);
        } 
      else
        {
          u_int32_t spi = ((u_int32_t *)spis)[i];

          sa = ipsec_sa_lookup (iaddr, spi, proto);
        }

      if (sa == NULL)
        {
	  LOG_DBG ((LOG_SA, 30, "ipsec_delete_spi_list: "
		   "could not locate SA (SPI %08x, proto %u)",
		   spis[i], proto));
	  continue;
	}

      /* Delete the SA and search for the next */
      LOG_DBG ((LOG_SA, 30, "ipsec_delete_spi_list: "
	       "%s made us delete SA %p (%d references) for proto %d",
	       type, sa, sa->refcnt, proto));

      sa_free (sa);
    }
}

/*
 * deal with a NOTIFY of INVALID_SPI
 */
static void
ipsec_invalid_spi (struct message *msg, struct payload *p)
{
  struct sockaddr *dst;
  int invspisz, off, dstlen;
  u_int32_t spi;
  u_int16_t totsiz;
  u_int8_t spisz;

  /* 
   * get the invalid spi out of the variable sized notification data
   * field, which is after the variable sized SPI field [which specifies
   * the receiving entity's phase-1 SPI, not the invalid spi]
   */
  totsiz = GET_ISAKMP_GEN_LENGTH (p->p);
  spisz = GET_ISAKMP_NOTIFY_SPI_SZ (p->p);
  off = ISAKMP_NOTIFY_SPI_OFF + spisz;
  invspisz = totsiz - off;

  if (invspisz != sizeof spi)
    {
      LOG_DBG ((LOG_SA, 40,
	       "ipsec_invalid_spi: SPI size %d in INVALID_SPI "
	       "payload unsupported", spisz));
       return;
    }
  memcpy (&spi, p->p + off, sizeof spi);

  msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);

  /* delete matching SPI's from this peer */
  ipsec_delete_spi_list (dst, 0, (u_int8_t *)&spi, 1, "INVALID_SPI");
}

static int
ipsec_responder (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  int (**script) (struct message *msg) = 0;
  struct payload *p;
  u_int16_t type;

  /* Check that a new exchange is coherent with the IKE rules.  */
  if (exchange->step == 0
      && ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT
	   && exchange->type != ISAKMP_EXCH_AGGRESSIVE
	   && exchange->type != ISAKMP_EXCH_INFO)
	  || (exchange->phase == 2 && exchange->type == ISAKMP_EXCH_ID_PROT)))
    {
      message_drop (msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 1, 0);
      return -1;
    }

  LOG_DBG ((LOG_MISC, 30,
	    "ipsec_responder: phase %d exchange %d step %d", exchange->phase,
	    exchange->type, exchange->step));
  switch (exchange->type)
    {
    case ISAKMP_EXCH_ID_PROT:
      script = ike_main_mode_responder;
      break;

#ifdef USE_AGGRESSIVE
    case ISAKMP_EXCH_AGGRESSIVE:
      script = ike_aggressive_responder;
      break;
#endif

    case ISAKMP_EXCH_INFO:
      for (p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_NOTIFY]); p;
	   p = TAILQ_NEXT (p, link))
	{
          type = GET_ISAKMP_NOTIFY_MSG_TYPE (p->p);
	  LOG_DBG ((LOG_EXCHANGE, 10,
		    "ipsec_responder: got NOTIFY of type %s",
		    constant_lookup (isakmp_notify_cst, type)));

          if (type == ISAKMP_NOTIFY_INVALID_SPI)
              ipsec_invalid_spi (msg, p);

	  p->flags |= PL_MARK;
	}

      /*
       * If any DELETEs are in here, let the logic of leftover payloads deal
       * with them.
       */

      return 0;

    case IKE_EXCH_QUICK_MODE:
      script = ike_quick_mode_responder;
      break;

    default:
      message_drop (msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 1, 0);
      return -1;
    }

  /* Run the script code for this step.  */
  if (script)
    return script[exchange->step] (msg);

  /*
   * XXX So far we don't accept any proposals for exchanges we don't support.
   */
  if (TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]))
    {
      message_drop (msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
      return -1;
    }
  return 0;
}

static enum hashes from_ike_hash (u_int16_t hash)
{
  switch (hash)
    {
    case IKE_HASH_MD5:
      return HASH_MD5;
    case IKE_HASH_SHA:
      return HASH_SHA1;
    }
  return -1;
}

static enum transform from_ike_crypto (u_int16_t crypto)
{
  /* Coincidentally this is the null operation :-)  */
  return crypto;
}

/*
 * Find out whether the attribute of type TYPE with a LEN length value
 * pointed to by VALUE is incompatible with what we can handle.
 * VMSG is a pointer to the current message.
 */
int
ipsec_is_attribute_incompatible (u_int16_t type, u_int8_t *value,
				 u_int16_t len, void *vmsg)
{
  struct message *msg = vmsg;

  if (msg->exchange->phase == 1)
    {
      switch (type)
	{
	case IKE_ATTR_ENCRYPTION_ALGORITHM:
	  return !crypto_get (from_ike_crypto (decode_16 (value)));
	case IKE_ATTR_HASH_ALGORITHM:
	  return !hash_get (from_ike_hash (decode_16 (value)));
	case IKE_ATTR_AUTHENTICATION_METHOD:
	  return !ike_auth_get (decode_16 (value));
	case IKE_ATTR_GROUP_DESCRIPTION:
	  return decode_16 (value) < IKE_GROUP_DESC_MODP_768
	    || decode_16 (value) > IKE_GROUP_DESC_MODP_1536;
	case IKE_ATTR_GROUP_TYPE:
	  return 1;
	case IKE_ATTR_GROUP_PRIME:
	  return 1;
	case IKE_ATTR_GROUP_GENERATOR_1:
	  return 1;
	case IKE_ATTR_GROUP_GENERATOR_2:
	  return 1;
	case IKE_ATTR_GROUP_CURVE_A:
	  return 1;
	case IKE_ATTR_GROUP_CURVE_B:
	  return 1;
	case IKE_ATTR_LIFE_TYPE:
	  return decode_16 (value) < IKE_DURATION_SECONDS
	    || decode_16 (value) > IKE_DURATION_KILOBYTES;
	case IKE_ATTR_LIFE_DURATION:
	  return len != 2 && len != 4;
	case IKE_ATTR_PRF:
	  return 1;
	case IKE_ATTR_KEY_LENGTH:
	  /*
	   * Our crypto routines only allows key-lengths which are multiples
	   * of an octet.
	   */
	  return decode_16 (value) % 8 != 0;
	case IKE_ATTR_FIELD_SIZE:
	  return 1;
	case IKE_ATTR_GROUP_ORDER:
	  return 1;
	}
    }
  else
    {
      switch (type)
	{
	case IPSEC_ATTR_SA_LIFE_TYPE:
	  return decode_16 (value) < IPSEC_DURATION_SECONDS
	    || decode_16 (value) > IPSEC_DURATION_KILOBYTES;
	case IPSEC_ATTR_SA_LIFE_DURATION:
	  return len != 2 && len != 4;
	case IPSEC_ATTR_GROUP_DESCRIPTION:
	  return decode_16 (value) < IKE_GROUP_DESC_MODP_768
	    || decode_16 (value) > IKE_GROUP_DESC_MODP_1536;
	case IPSEC_ATTR_ENCAPSULATION_MODE:
	  return decode_16 (value) < IPSEC_ENCAP_TUNNEL
	    || decode_16 (value) > IPSEC_ENCAP_TRANSPORT;
	case IPSEC_ATTR_AUTHENTICATION_ALGORITHM:
	  return decode_16 (value) < IPSEC_AUTH_HMAC_MD5
	    || decode_16 (value) > IPSEC_AUTH_KPDK;
	case IPSEC_ATTR_KEY_LENGTH:
	  /* XXX Blowfish needs '0'. Others appear to disregard this attr?  */
	  return 0;
	case IPSEC_ATTR_KEY_ROUNDS:
	  return 1;
	case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE:
	  return 1;
	case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM:
	  return 1;
	}
    }
  /* XXX Silence gcc.  */
  return 1;
}

#ifdef USE_DEBUG
/*
 * Log the attribute of TYPE with a LEN length value pointed to by VALUE
 * in human-readable form.  VMSG is a pointer to the current message.
 */
int
ipsec_debug_attribute (u_int16_t type, u_int8_t *value, u_int16_t len,
		       void *vmsg)
{
  struct message *msg = vmsg;
  char val[20];

  /* XXX Transient solution.  */
  if (len == 2)
    sprintf (val, "%d", decode_16 (value));
  else if (len == 4)
    sprintf (val, "%d", decode_32 (value));
  else
    sprintf (val, "unrepresentable");

  LOG_DBG ((LOG_MESSAGE, 50, "Attribute %s value %s",
	    constant_name (msg->exchange->phase == 1
			   ? ike_attr_cst : ipsec_attr_cst, type),
	    val));
  return 0;
}
#endif

/*
 * Decode the attribute of type TYPE with a LEN length value pointed to by
 * VALUE.  VIDA is a pointer to a context structure where we can find the
 * current message, SA and protocol.
 */
int
ipsec_decode_attribute (u_int16_t type, u_int8_t *value, u_int16_t len,
			void *vida)
{
  struct ipsec_decode_arg *ida = vida;
  struct message *msg = ida->msg;
  struct sa *sa = ida->sa;
  struct ipsec_sa *isa = sa->data;
  struct proto *proto = ida->proto;
  struct ipsec_proto *iproto = proto->data;
  struct exchange *exchange = msg->exchange;
  struct ipsec_exch *ie = exchange->data;
  static int lifetype = 0;

  if (exchange->phase == 1)
    {
      switch (type)
	{
	case IKE_ATTR_ENCRYPTION_ALGORITHM:
	  /* XXX Errors possible?  */
	  exchange->crypto = crypto_get (from_ike_crypto (decode_16 (value)));
	  break;
	case IKE_ATTR_HASH_ALGORITHM:
	  /* XXX Errors possible?  */
	  ie->hash = hash_get (from_ike_hash (decode_16 (value)));
	  break;
	case IKE_ATTR_AUTHENTICATION_METHOD:
	  /* XXX Errors possible?  */
	  ie->ike_auth = ike_auth_get (decode_16 (value));
	  break;
	case IKE_ATTR_GROUP_DESCRIPTION:
	  isa->group_desc = decode_16 (value);
	  break;
	case IKE_ATTR_GROUP_TYPE:
	  break;
	case IKE_ATTR_GROUP_PRIME:
	  break;
	case IKE_ATTR_GROUP_GENERATOR_1:
	  break;
	case IKE_ATTR_GROUP_GENERATOR_2:
	  break;
	case IKE_ATTR_GROUP_CURVE_A:
	  break;
	case IKE_ATTR_GROUP_CURVE_B:
	  break;
	case IKE_ATTR_LIFE_TYPE:
	  lifetype = decode_16 (value);
	  return 0;
	case IKE_ATTR_LIFE_DURATION:
	  switch (lifetype)
	    {
	    case IKE_DURATION_SECONDS:
	      switch (len)
		{
		case 2:
		  sa->seconds = decode_16 (value);
		  break;
		case 4:
		  sa->seconds = decode_32 (value);
		  break;
		default:
		  /* XXX Log.  */
		}
	      break;
	    case IKE_DURATION_KILOBYTES:
	      switch (len)
		{
		case 2:
		  sa->kilobytes = decode_16 (value);
		  break;
		case 4:
		  sa->kilobytes = decode_32 (value);
		  break;
		default:
		  /* XXX Log.  */
		}
	      break;
	    default:
	      /* XXX Log!  */
	    }
	  break;
	case IKE_ATTR_PRF:
	  break;
	case IKE_ATTR_KEY_LENGTH:
	  exchange->key_length = decode_16 (value) / 8;
	  break;
	case IKE_ATTR_FIELD_SIZE:
	  break;
	case IKE_ATTR_GROUP_ORDER:
	  break;
	}
    }
  else
    {
      switch (type)
	{
	case IPSEC_ATTR_SA_LIFE_TYPE:
	  lifetype = decode_16 (value);
	  return 0;
	case IPSEC_ATTR_SA_LIFE_DURATION:
	  switch (lifetype)
	    {
	    case IPSEC_DURATION_SECONDS:
	      switch (len)
		{
		case 2:
		  sa->seconds = decode_16 (value);
		  break;
		case 4:
		  sa->seconds = decode_32 (value);
		  break;
		default:
		  /* XXX Log.  */
		}
	      break;
	    case IPSEC_DURATION_KILOBYTES:
	      switch (len)
		{
		case 2:
		  sa->kilobytes = decode_16 (value);
		  break;
		case 4:
		  sa->kilobytes = decode_32 (value);
		  break;
		default:
		  /* XXX Log.  */
		}
	      break;
	    default:
	      /* XXX Log!  */
	    }
	  break;
	case IPSEC_ATTR_GROUP_DESCRIPTION:
	  isa->group_desc = decode_16 (value);
	  break;
	case IPSEC_ATTR_ENCAPSULATION_MODE:
	  /* XXX Multiple protocols must have same encapsulation mode, no?  */
	  iproto->encap_mode = decode_16 (value);
	  break;
	case IPSEC_ATTR_AUTHENTICATION_ALGORITHM:
	  iproto->auth = decode_16 (value);
	  break;
	case IPSEC_ATTR_KEY_LENGTH:
	  iproto->keylen = decode_16 (value);
	  break;
	case IPSEC_ATTR_KEY_ROUNDS:
	  iproto->keyrounds = decode_16 (value);
	  break;
	case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE:
	  break;
	case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM:
	  break;
	}
    }
  lifetype = 0;
  return 0;
}

/*
 * Walk over the attributes of the transform payload found in BUF, and
 * fill out the fields of the SA attached to MSG.  Also mark the SA as
 * processed.
 */
void
ipsec_decode_transform (struct message *msg, struct sa *sa,
			struct proto *proto, u_int8_t *buf)
{
  struct ipsec_exch *ie = msg->exchange->data;
  struct ipsec_decode_arg ida;

  LOG_DBG ((LOG_MISC, 20, "ipsec_decode_transform: transform %d chosen",
	    GET_ISAKMP_TRANSFORM_NO (buf)));

  ida.msg = msg;
  ida.sa = sa;
  ida.proto = proto;

  /* The default IKE lifetime is 8 hours.  */
  if (sa->phase == 1)
    sa->seconds = 28800;

  /* Extract the attributes and stuff them into the SA.  */
  attribute_map (buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		 GET_ISAKMP_GEN_LENGTH (buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		 ipsec_decode_attribute, &ida);

  /*
   * If no pseudo-random function was negotiated, it's HMAC.
   * XXX As PRF_HMAC currently is zero, this is a no-op.
   */
  if (!ie->prf_type)
    ie->prf_type = PRF_HMAC;
}

/*
 * Delete the IPSec SA represented by the INCOMING direction in protocol PROTO
 * of the IKE security association SA.
 */
static void
ipsec_delete_spi (struct sa *sa, struct proto *proto, int incoming)
{
  if (sa->phase == 1)
    return;
  /* XXX Error handling?  Is it interesting?  */
  sysdep_ipsec_delete_spi (sa, proto, incoming);
}

/*
 * Store BUF into the g^x entry of the exchange that message MSG belongs to.
 * PEER is non-zero when the value is our peer's, and zero when it is ours.
 */
static int
ipsec_g_x (struct message *msg, int peer, u_int8_t *buf)
{
  struct exchange *exchange = msg->exchange;
  struct ipsec_exch *ie = exchange->data;
  u_int8_t **g_x;
  int initiator = exchange->initiator ^ peer;
  char header[32];

  g_x = initiator ? &ie->g_xi : &ie->g_xr;
  *g_x = malloc (ie->g_x_len);
  if (!*g_x)
    {
      log_error ("ipsec_g_x: malloc (%d) failed", ie->g_x_len);
      return -1;
    }
  memcpy (*g_x, buf, ie->g_x_len);
  snprintf (header, 32, "ipsec_g_x: g^x%c", initiator ? 'i' : 'r');
  LOG_DBG_BUF ((LOG_MISC, 80, header, *g_x, ie->g_x_len));
  return 0;
}

/* Generate our DH value.  */
int
ipsec_gen_g_x (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  struct ipsec_exch *ie = exchange->data;
  u_int8_t *buf;

  buf = malloc (ISAKMP_KE_SZ + ie->g_x_len);
  if (!buf)
    {
      log_error ("ipsec_gen_g_x: malloc (%d) failed",
		 ISAKMP_KE_SZ + ie->g_x_len);
      return -1;
    }

  if (message_add_payload (msg, ISAKMP_PAYLOAD_KEY_EXCH, buf,
			   ISAKMP_KE_SZ + ie->g_x_len, 1))
    {
      free (buf);
      return -1;
    }

  if (dh_create_exchange (ie->group, buf + ISAKMP_KE_DATA_OFF))
    {
      log_print ("ipsec_gen_g_x: dh_create_exchange failed");
      free (buf);
      return -1;
    }
  return ipsec_g_x (msg, 0, buf + ISAKMP_KE_DATA_OFF);
}

/* Save the peer's DH value.  */
int
ipsec_save_g_x (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  struct ipsec_exch *ie = exchange->data;
  struct payload *kep;

  kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]);
  kep->flags |= PL_MARK;
  ie->g_x_len = GET_ISAKMP_GEN_LENGTH (kep->p) - ISAKMP_KE_DATA_OFF;

  /* Check that the given length matches the group's expectancy.  */
  if (ie->g_x_len != dh_getlen (ie->group))
    {
      /* XXX Is this a good notify type?  */
      message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
      return -1;
    }

  return ipsec_g_x (msg, 1, kep->p + ISAKMP_KE_DATA_OFF);
}

/*
 * Get a SPI for PROTO and the transport MSG passed over.  Store the
 * size where SZ points.  NB!  A zero return is OK if *SZ is zero.
 */
static u_int8_t *
ipsec_get_spi (size_t *sz, u_int8_t proto, struct message *msg)
{
  struct sockaddr *dst, *src;
  int dstlen, srclen;
  struct transport *transport = msg->transport;

  if (msg->exchange->phase == 1)
    {
      *sz = 0;
      return 0;
    }
  else
    {
      /* We are the destination in the SA we want a SPI for.  */
      transport->vtbl->get_src (transport, &dst, &dstlen);
      /* The peer is the source.  */
      transport->vtbl->get_dst (transport, &src, &srclen);
      return sysdep_ipsec_get_spi (sz, proto, src, srclen, dst, dstlen,
				   msg->exchange->seq);
    }
}

/*
 * We have gotten a payload PAYLOAD of type TYPE, which did not get handled
 * by the logic of the exchange MSG takes part in.  Now is the time to deal
 * with such a payload if we know how to, if we don't, return -1, otherwise
 * 0.
 */
int
ipsec_handle_leftover_payload (struct message *msg, u_int8_t type,
			       struct payload *payload)
{
  u_int32_t spisz, nspis;
  struct sockaddr *dst;
  socklen_t dstlen;
  int reenter = 0;
  u_int8_t *spis, proto;
  struct sa *sa;

  switch (type)
    {
    case ISAKMP_PAYLOAD_DELETE:
      proto = GET_ISAKMP_DELETE_PROTO (payload->p);
      nspis = GET_ISAKMP_DELETE_NSPIS (payload->p);
      spisz = GET_ISAKMP_DELETE_SPI_SZ (payload->p);

      if (nspis == 0)
        {
	  LOG_DBG ((LOG_SA, 60, "ipsec_handle_leftover_payload: message "
		    "specified zero SPIs, ignoring"));
	  return -1;
	}

      /* verify proper SPI size */
      if ((proto == ISAKMP_PROTO_ISAKMP && spisz != ISAKMP_HDR_COOKIES_LEN)
          || (proto != ISAKMP_PROTO_ISAKMP && spisz != sizeof (u_int32_t)))
        {
	  log_print ("ipsec_handle_leftover_payload: "
		     "invalid SPI size %d for proto %d in DELETE payload",
		     spisz, proto);
	  return -1;
        }

      spis = (u_int8_t *)malloc (nspis * spisz);
      if (!spis)
        {
	  log_error ("ipsec_handle_leftover_payload: malloc (%d) failed",
		     nspis * spisz);
	  return -1;
	}

      /* extract SPI and get dst address */
      memcpy (spis, payload->p + ISAKMP_DELETE_SPI_OFF, nspis * spisz);
      msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);

      ipsec_delete_spi_list (dst, proto, spis, nspis, "DELETE");

      free (spis);
      payload->flags |= PL_MARK;
      return 0;

    case ISAKMP_PAYLOAD_NOTIFY:
      switch (GET_ISAKMP_NOTIFY_MSG_TYPE (payload->p))
	{
	case IPSEC_NOTIFY_INITIAL_CONTACT:
	  /*
	   * Find out who is sending this and then delete every SA that is
	   * ready.  Exchanges will timeout themselves and then the
	   * non-ready SAs will disappear too.
	   */
	  msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);
	  while ((sa = sa_lookup_by_peer (dst, dstlen)) != 0)
	    {
	      /*
	       * Don't delete the current SA -- we received the notification
	       * over it, so it's obviously still active. We temporarily need
               * to remove the SA from the list to avoid an endless loop,
	       * but keep a reference so it won't disappear meanwhile.
	       */
	      if (sa == msg->isakmp_sa)
	        {
		  sa_reference (sa);
                  sa_remove (sa);
                  reenter = 1;
		  continue;
		}

	      LOG_DBG ((LOG_SA, 30,
			"ipsec_handle_leftover_payload: "
			"INITIAL-CONTACT made us delete SA %p",
			sa));
	      sa_delete (sa, 0);
	    }

          if (reenter)
	    {
	      sa_enter (msg->isakmp_sa);
	      sa_release (msg->isakmp_sa);
	    }
	  payload->flags |= PL_MARK;
	  return 0;
	}
    }
  return -1;
}

/* Return the encryption keylength in octets of the ESP protocol PROTO.  */
int
ipsec_esp_enckeylength (struct proto *proto)
{
  struct ipsec_proto *iproto = proto->data;

  /* Compute the keylength to use.  */
  switch (proto->id)
    {
    case IPSEC_ESP_DES:
    case IPSEC_ESP_DES_IV32:
    case IPSEC_ESP_DES_IV64:
      return 8;
    case IPSEC_ESP_3DES:
      return 24;
    case IPSEC_ESP_CAST:
      if (!iproto->keylen)
        return 16;
      return iproto->keylen / 8;
    case IPSEC_ESP_AES:
      if (!iproto->keylen)
	return 16;
      /* Fallthrough */
    default:
      return iproto->keylen / 8;
    }
}

/* Return the authentication keylength in octets of the ESP protocol PROTO.  */
int
ipsec_esp_authkeylength (struct proto *proto)
{
  struct ipsec_proto *iproto = proto->data;

  switch (iproto->auth)
    {
    case IPSEC_AUTH_HMAC_MD5:
      return 16;
    case IPSEC_AUTH_HMAC_SHA:
    case IPSEC_AUTH_HMAC_RIPEMD:
      return 20;
    default:
      return 0;
    }
}

/* Return the authentication keylength in octets of the AH protocol PROTO.  */
int
ipsec_ah_keylength (struct proto *proto)
{
  switch (proto->id)
    {
    case IPSEC_AH_MD5:
      return 16;
    case IPSEC_AH_SHA:
    case IPSEC_AH_RIPEMD:
      return 20;
    default:
      return -1;
    }
}

/* Return the total keymaterial length of the protocol PROTO.  */
int
ipsec_keymat_length (struct proto *proto)
{
  switch (proto->proto)
    {
    case IPSEC_PROTO_IPSEC_ESP:
      return ipsec_esp_enckeylength (proto) + ipsec_esp_authkeylength (proto);
    case IPSEC_PROTO_IPSEC_AH:
      return ipsec_ah_keylength (proto);
    default:
      return -1;
    }
}

/*
 * Out of a named section SECTION in the configuration file find out
 * the network address and mask as well as the ID type.  Put the info
 * in the areas pointed to by ADDR, MASK, TPROTO, PORT, and ID respectively.
 * Return 0 on success and -1 on failure.
 */
int
ipsec_get_id (char *section, int *id, struct in_addr *addr,
	      struct in_addr *mask, u_int8_t *tproto, u_int16_t *port)
{
  char *type, *address, *netmask;

  type = conf_get_str (section, "ID-type");
  if (!type)
    {
      log_print ("ipsec_get_id: section %s has no \"ID-type\" tag", section);
      return -1;
    }

  *id = constant_value (ipsec_id_cst, type);
  switch (*id)
    {
    case IPSEC_ID_IPV4_ADDR:
      address = conf_get_str (section, "Address");
      if (!address)
	{
	  log_print ("ipsec_get_id: section %s has no \"Address\" tag",
		     section);
	  return -1;
	}

      if (!inet_aton (address, addr))
	{
	  log_print ("ipsec_get_id: invalid address %s in section %s", section,
		     address);
	  return -1;
	}

      *tproto = conf_get_num (section, "Protocol", 0);
      if (*tproto)
	*port = conf_get_num (section, "Port", 0);
      break;

#ifdef notyet
    case IPSEC_ID_FQDN:
      return -1;

    case IPSEC_ID_USER_FQDN:
      return -1;
#endif

    case IPSEC_ID_IPV4_ADDR_SUBNET:
      address = conf_get_str (section, "Network");
      if (!address)
	{
	  log_print ("ipsec_get_id: section %s has no \"Network\" tag",
		     section);
	  return -1;
	}

      if (!inet_aton (address, addr))
	{
	  log_print ("ipsec_get_id: invalid section %s network %s", section,
		     address);
	  return -1;
	}

      netmask = conf_get_str (section, "Netmask");
      if (!netmask)
	{
	  log_print ("ipsec_get_id: section %s has no \"Netmask\" tag",
		     section);
	  return -1;
	}

      if (!inet_aton (netmask, mask))
	{
	  log_print ("ipsec_id_build: invalid section %s network %s", section,
		     netmask);
	  return -1;
	}

      *tproto = conf_get_num (section, "Protocol", 0);
      if (*tproto)
	*port = conf_get_num (section, "Port", 0);
      break;

#ifdef notyet
    case IPSEC_ID_IPV6_ADDR:
      return -1;

    case IPSEC_ID_IPV6_ADDR_SUBNET:
      return -1;

    case IPSEC_ID_IPV4_RANGE:
      return -1;

    case IPSEC_ID_IPV6_RANGE:
      return -1;

    case IPSEC_ID_DER_ASN1_DN:
      return -1;

    case IPSEC_ID_DER_ASN1_GN:
      return -1;

    case IPSEC_ID_KEY_ID:
      return -1;
#endif

    default:
      log_print ("ipsec_get_id: unknown ID type \"%s\" in section %s", type,
		 section);
      return -1;
    }

  return 0;
}

static void
ipsec_ipv4toa (char *buf, size_t size, u_int8_t *addr)
{
#ifdef HAVE_GETNAMEINFO
  struct sockaddr_storage from;
  struct sockaddr_in *sfrom = (struct sockaddr_in *)&from;
  socklen_t fromlen = sizeof from;

  memset (&from, 0, fromlen);
  sfrom->sin_len = sizeof *sfrom;
  sfrom->sin_family = AF_INET;
  memcpy (&sfrom->sin_addr.s_addr, addr, sizeof sfrom->sin_addr.s_addr);

  if (getnameinfo ((struct sockaddr *)sfrom, sfrom->sin_len, buf, size, NULL,
		   0, NI_NUMERICHOST) != 0)
    {
      log_print ("ipsec_ipv4toa: getnameinfo () failed");
      strcpy (buf, "<error>");
    }
#else
  strncpy (buf, inet_ntoa (*(struct in_addr *)addr), size - 1);
  buf[size - 1] = '\0';
#endif /* HAVE_GETNAMEINFO */
}

static void
ipsec_decode_id (u_int8_t *buf, int size, u_int8_t *id, size_t id_len,
		 int isakmpform)
{
  int id_type;
  char ntop[NI_MAXHOST], ntop2[NI_MAXHOST];

  if (id)
    {
      if (!isakmpform)
	{
	  /* exchanges and SA's dont carry the IDs in ISAKMP form */
	  id -= ISAKMP_ID_TYPE_OFF;
	  id_len += ISAKMP_ID_TYPE_OFF;
	}

      id_type = GET_ISAKMP_ID_TYPE (id);
      switch (id_type)
	{
	case IPSEC_ID_IPV4_ADDR:
	  ipsec_ipv4toa (ntop, sizeof ntop, id + ISAKMP_ID_DATA_OFF);
	  snprintf (buf, size, "%08x: %s",
		    decode_32 (id + ISAKMP_ID_DATA_OFF), ntop);
	  break;
	case IPSEC_ID_IPV4_ADDR_SUBNET:
	  ipsec_ipv4toa (ntop, sizeof ntop, id + ISAKMP_ID_DATA_OFF);
	  ipsec_ipv4toa (ntop2, sizeof ntop2, id + ISAKMP_ID_DATA_OFF + 4);
	  snprintf (buf, size, "%08x/%08x: %s/%s",
		    decode_32 (id + ISAKMP_ID_DATA_OFF),
		    decode_32 (id + ISAKMP_ID_DATA_OFF + 4),
		    ntop, ntop2);
	  break;
	case IPSEC_ID_FQDN:
	case IPSEC_ID_USER_FQDN:
	  /* String is not NUL terminated, be careful */
	  id_len -= ISAKMP_ID_DATA_OFF;
	  id_len = MIN(id_len, size - 1);
	  memcpy (buf, id + ISAKMP_ID_DATA_OFF, id_len);
	  buf[id_len] = '\0';
	  break;
	  /* XXX - IPV6 et al */
	default:
	  snprintf (buf, size, "<type unknown: %x>", id_type);
	  break;
	}
    }
  else
    snprintf (buf, size, "<no id>");
}

char *
ipsec_decode_ids (char *fmt, u_int8_t *id1, size_t id1_len,
		  u_int8_t *id2, size_t id2_len, int isakmpform)
{
  static char result[1024];
  char s_id1[256], s_id2[256];

  ipsec_decode_id (s_id1, sizeof s_id1, id1, id1_len, isakmpform);
  ipsec_decode_id (s_id2, sizeof s_id2, id2, id2_len, isakmpform);

  snprintf (result, sizeof result, fmt, s_id1, s_id2);
  return result;
}

/*
 * Out of a named section SECTION in the configuration file build an
 * ISAKMP ID payload.  Ths payload size should be stashed in SZ.
 * The caller is responsible for freeing the payload.
 */
u_int8_t *
ipsec_build_id (char *section, size_t *sz)
{
  struct in_addr addr, mask;
  u_int8_t *p;
  int id;
  u_int8_t tproto = 0;
  u_int16_t port = 0;

  if (ipsec_get_id (section, &id, &addr, &mask, &tproto, &port))
    return 0;

  *sz = ISAKMP_ID_SZ;
  switch (id)
    {
    case IPSEC_ID_IPV4_ADDR:
      *sz += sizeof addr;
      break;
    case IPSEC_ID_IPV4_ADDR_SUBNET:
      *sz += sizeof addr + sizeof mask;
      break;
    }

  p = malloc (*sz);
  if (!p)
    {
      log_print ("ipsec_build_id: malloc(%d) failed", *sz);
      return 0;
    }

  SET_ISAKMP_ID_TYPE (p, id);
  SET_ISAKMP_ID_DOI_DATA (p, "\000\000\000");

  switch (id)
    {
    case IPSEC_ID_IPV4_ADDR:
      encode_32 (p + ISAKMP_ID_DATA_OFF, ntohl (addr.s_addr));
      SET_IPSEC_ID_PROTO (p + ISAKMP_ID_DOI_DATA_OFF, tproto);
      SET_IPSEC_ID_PORT (p + ISAKMP_ID_DOI_DATA_OFF, port);
      break;
    case IPSEC_ID_IPV4_ADDR_SUBNET:
      encode_32 (p + ISAKMP_ID_DATA_OFF, ntohl (addr.s_addr));
      encode_32 (p + ISAKMP_ID_DATA_OFF + 4, ntohl (mask.s_addr));
      SET_IPSEC_ID_PROTO (p + ISAKMP_ID_DOI_DATA_OFF, tproto);
      SET_IPSEC_ID_PORT (p + ISAKMP_ID_DOI_DATA_OFF, port);
      break;
    }

  return p;
}

/*
 * copy an ISAKMPD id
 */

int
ipsec_clone_id (u_int8_t **did, size_t *did_len, u_int8_t *id, size_t id_len)
{
  if (*did)
    free (*did);

  if (!id_len || id == NULL)
    {
      *did = NULL;
      *did_len = 0;
      return 0;
    }

  *did = malloc (id_len);
  if (*did == NULL)
    {
      *did_len = 0;
      log_error ("ipsec_clone_id: malloc(%d) failed", id_len);
      return -1;
    }

  *did_len = id_len;
  memcpy (*did, id, id_len);

  return 0;
}

/*
 * IPSec-specific PROTO initializations.  SECTION is only set if we are the
 * initiator thus only usable there.
 * XXX I want to fix this later.
 */
void
ipsec_proto_init (struct proto *proto, char *section)
{
  struct ipsec_proto *iproto = proto->data;

  if (proto->sa->phase == 2 && section)
    iproto->replay_window
      = conf_get_num (section, "ReplayWindow", DEFAULT_REPLAY_WINDOW);
}

/*
 * Add a notification payload of type INITIAL CONTACT to MSG if this is
 * the first contact we have made to our peer.
 */
int
ipsec_initial_contact (struct message *msg)
{
  u_int8_t *buf;

  if (ipsec_contacted (msg))
    return 0;

  buf = malloc (ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN);
  if (!buf)
    {
      log_error ("ike_phase_1_initial_contact: malloc (%d) failed",
		 ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN);
      return -1;
    }
  SET_ISAKMP_NOTIFY_DOI (buf, IPSEC_DOI_IPSEC);
  SET_ISAKMP_NOTIFY_PROTO (buf, ISAKMP_PROTO_ISAKMP);
  SET_ISAKMP_NOTIFY_SPI_SZ (buf, ISAKMP_HDR_COOKIES_LEN);
  SET_ISAKMP_NOTIFY_MSG_TYPE (buf, IPSEC_NOTIFY_INITIAL_CONTACT);
  memcpy (buf + ISAKMP_NOTIFY_SPI_OFF, msg->isakmp_sa->cookies,
	  ISAKMP_HDR_COOKIES_LEN);
  if (message_add_payload (msg, ISAKMP_PAYLOAD_NOTIFY, buf,
			   ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN, 1))
    {
      free (buf);
      return -1;
    }

  return ipsec_add_contact (msg);
}

/*
 * Compare the two contacts pointed to by A and B.  Return negative if
 * *A < *B, 0 if they are equal, and positive if *A is the largest of them.
 */
static int
addr_cmp (const void *a, const void *b)
{
  const struct contact *x = a, *y = b;
  int minlen = MIN (x->len, y->len);
  int rv = memcmp (x->addr, y->addr, minlen);

  return rv ? rv : (x->len - y->len);
}

/*
 * Add the peer that MSG is bound to as an address we don't want to send
 * INITIAL CONTACT too from now on.  Do not call this function with a
 * specific address duplicate times. We want fast lookup, speed of insertion
 * is unimportant, if this is to scale.
 */
static int
ipsec_add_contact (struct message *msg)
{
  struct contact *new_contacts;
  struct sockaddr *dst, *addr;
  socklen_t dstlen;
  int cnt;

  if (contact_cnt == contact_limit)
    {
      cnt = contact_limit ? 2 * contact_limit : 64;
      new_contacts = realloc (contacts, cnt * sizeof contacts[0]);
      if (!new_contacts)
	{
	  log_error ("ipsec_add_contact: realloc (%p, %d) failed", contacts,
		     cnt * sizeof contacts[0]);
	  return -1;
	}
      contact_limit = cnt;
      contacts = new_contacts;
    }
  msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);
  addr = malloc (dstlen);
  if (!addr)
    {
      log_error ("ipsec_add_contact: malloc (%d) failed", dstlen);
      return -1;
    }
  memcpy (addr, dst, dstlen);
  contacts[contact_cnt].addr = addr;
  contacts[contact_cnt++].len = dstlen;

  /*
   * XXX There are better algorithms for already mostly-sorted data like
   * this, but only qsort is standard.  I will someday do this inline.
   */
  qsort (contacts, contact_cnt, sizeof *contacts, addr_cmp);
  return 0;
}

/* Return true if the recipient of MSG has already been contacted.  */
static int
ipsec_contacted (struct message *msg)
{
  struct contact contact;

  msg->transport->vtbl->get_dst (msg->transport, &contact.addr, &contact.len);
  return contacts
    ? (bsearch (&contact, contacts, contact_cnt, sizeof *contacts, addr_cmp)
       != 0)
    : 0;
}

/* Add a HASH for to MSG.  */
u_int8_t *
ipsec_add_hash_payload (struct message *msg, size_t hashsize)
{
  u_int8_t *buf;

  buf = malloc (ISAKMP_HASH_SZ + hashsize);
  if (!buf)
    {
      log_error ("ipsec_add_hash_payload: malloc (%d) failed",
		 ISAKMP_HASH_SZ + hashsize);
      return 0;
    }

  if (message_add_payload (msg, ISAKMP_PAYLOAD_HASH, buf,
			   ISAKMP_HASH_SZ + hashsize, 1))
    {
      free (buf);
      return 0;
    }

  return buf;
}

/* Fill in the HASH payload of MSG.  */
int
ipsec_fill_in_hash (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  struct sa *isakmp_sa = msg->isakmp_sa;
  struct ipsec_sa *isa = isakmp_sa->data;
  struct hash *hash = hash_get (isa->hash);
  struct prf *prf;
  struct payload *payload;
  u_int8_t *buf;
  int i;
  char header[80];

  /* If no SKEYID_a, we need not do anything.  */
  if (!isa->skeyid_a)
    return 0;

  payload = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]);
  if (!payload)
    {
      log_print ("ipsec_fill_in_hash: no HASH payload found");
      return -1;
    }
  buf = payload->p;

  /* Allocate the prf and start calculating our HASH(1).  */
  LOG_DBG_BUF ((LOG_MISC, 90, "ipsec_fill_in_hash: SKEYID_a", isa->skeyid_a,
		isa->skeyid_len));
  prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len);
  if (!prf)
    return -1;

  prf->Init (prf->prfctx);
  LOG_DBG_BUF ((LOG_MISC, 90, "ipsec_fill_in_hash: message_id",
		exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
  prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);

  /* Loop over all payloads after HASH(1).  */
  for (i = 2; i < msg->iovlen; i++)
    {
      /* XXX Misleading payload type printouts.  */
      snprintf (header, 80, "ipsec_fill_in_hash: payload %d after HASH(1)",
		i - 1);
      LOG_DBG_BUF ((LOG_MISC, 90, header, msg->iov[i].iov_base,
		    msg->iov[i].iov_len));
      prf->Update (prf->prfctx, msg->iov[i].iov_base, msg->iov[i].iov_len);
    }
  prf->Final (buf + ISAKMP_HASH_DATA_OFF, prf->prfctx);
  prf_free (prf);
  LOG_DBG_BUF ((LOG_MISC, 80, "ipsec_fill_in_hash: HASH(1)",
		buf + ISAKMP_HASH_DATA_OFF, hash->hashsize));

  return 0;
}

/* Add a HASH payload to MSG, if we have an ISAKMP SA we're protected by.  */
static int
ipsec_informational_pre_hook (struct message *msg)
{
  struct sa *isakmp_sa = msg->isakmp_sa;
  struct ipsec_sa *isa;
  struct hash *hash;

  if (!isakmp_sa)
    return 0;
  isa = isakmp_sa->data;
  hash = hash_get (isa->hash);
  return ipsec_add_hash_payload (msg, hash->hashsize) == 0;
}

/*
 * Fill in the HASH payload in MSG, if we have an ISAKMP SA we're protected by.
 */
static int
ipsec_informational_post_hook (struct message *msg)
{
  if (!msg->isakmp_sa)
    return 0;
  return ipsec_fill_in_hash (msg);
}

ssize_t
ipsec_id_size (char *section, u_int8_t *id)
{
  char *type, *data;

  type = conf_get_str (section, "ID-type");
  if (!type)
    {
      log_print ("ipsec_id_size: section %s has no \"ID-type\" tag", section);
      return -1;
    }

  *id = constant_value (ipsec_id_cst, type);
  switch (*id)
    {
    case IPSEC_ID_IPV4_ADDR:
      return sizeof (in_addr_t);
    case IPSEC_ID_IPV4_ADDR_SUBNET:
      return 2 * sizeof (in_addr_t);
    case IPSEC_ID_FQDN:
    case IPSEC_ID_USER_FQDN:
    case IPSEC_ID_KEY_ID:
      data = conf_get_str (section, "Name");
      if (!data)
	{
	  log_print ("ipsec_id_size: section %s has no \"Name\" tag", section);
	  return -1;
	}
      return strlen (data);
    }
  log_print ("ipsec_id_size: unrecognized ID-type %d (%s)", *id, type);
  return -1;
}