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

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

Revision 1.42, 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.41: +2 -2 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: message.c,v 1.42 2001/04/24 07:27:37 niklas Exp $	*/
/*	$EOM: message.c,v 1.156 2000/10/10 12:36:39 provos Exp $	*/

/*
 * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 1999 Angelos D. Keromytis.  All rights reserved.
 * Copyright (c) 1999, 2000 Håkan Olsson.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 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 <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

#include "sysdep.h"

#include "attribute.h"
#include "cert.h"
#include "constants.h"
#include "crypto.h"
#include "doi.h"
#include "exchange.h"
#include "field.h"
#include "isakmp.h"
#include "log.h"
#include "message.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"
#include "util.h"

#ifdef __GNUC__
#define INLINE __inline
#else
#define INLINE
#endif

/* A local set datatype, coincidentally fd_set suits our purpose fine.  */
typedef fd_set set;
#define ISSET FD_ISSET
#define SET FD_SET
#define ZERO FD_ZERO

static int message_check_duplicate (struct message *);
static int message_encrypt (struct message *);
static int message_index_payload (struct message *, struct payload *, u_int8_t,
				  u_int8_t *);
static int message_parse_transform (struct message *, struct payload *,
				    u_int8_t, u_int8_t *);
static int message_validate_cert (struct message *, struct payload *);
static int message_validate_cert_req (struct message *, struct payload *);
static int message_validate_delete (struct message *, struct payload *);
static int message_validate_hash (struct message *, struct payload *);
static int message_validate_id (struct message *, struct payload *);
static int message_validate_key_exch (struct message *, struct payload *);
static int message_validate_nonce (struct message *, struct payload *);
static int message_validate_notify (struct message *, struct payload *);
static int message_validate_proposal (struct message *, struct payload *);
static int message_validate_sa (struct message *, struct payload *);
static int message_validate_sig (struct message *, struct payload *);
static int message_validate_transform (struct message *, struct payload *);
static int message_validate_vendor (struct message *, struct payload *);

static void message_packet_log (struct message *);

static int (*message_validate_payload[]) (struct message *, struct payload *) =
{
  message_validate_sa, message_validate_proposal, message_validate_transform,
  message_validate_key_exch, message_validate_id, message_validate_cert,
  message_validate_cert_req, message_validate_hash, message_validate_sig,
  message_validate_nonce, message_validate_notify, message_validate_delete,
  message_validate_vendor
};

static struct field *fields[] = {
  isakmp_sa_fld, isakmp_prop_fld, isakmp_transform_fld, isakmp_ke_fld,
  isakmp_id_fld, isakmp_cert_fld, isakmp_certreq_fld, isakmp_hash_fld,
  isakmp_sig_fld, isakmp_nonce_fld, isakmp_notify_fld, isakmp_delete_fld,
  isakmp_vendor_fld
};

/*
 * Fields used for checking monotonic increasing of proposal and transform
 * numbers.
 */
static u_int8_t *last_sa = 0;
static int last_prop_no;
static u_int8_t *last_prop = 0;
static int last_xf_no;

/*
 * Allocate a message structure bound to transport T, and with a first
 * segment buffer sized SZ, copied from BUF if given.
 */
struct message *
message_alloc (struct transport *t, u_int8_t *buf, size_t sz)
{
  struct message *msg;
  int i;

  /*
   * We use calloc(3) because it zeroes the structure which we rely on in
   * message_free when determining what sub-allocations to free.
   */
  msg = (struct message *)calloc (1, sizeof *msg);
  if (!msg)
    return 0;
  msg->iov = calloc (1, sizeof *msg->iov);
  if (!msg->iov)
    {
      message_free (msg);
      return 0;
    }
  msg->iov[0].iov_len = sz;
  msg->iov[0].iov_base = malloc (sz);
  if (!msg->iov[0].iov_base)
    {
      message_free (msg);
      return 0;
    }
  msg->iovlen = 1;
  if (buf)
    memcpy (msg->iov[0].iov_base, buf, sz);
  msg->nextp = msg->iov[0].iov_base + ISAKMP_HDR_NEXT_PAYLOAD_OFF;
  msg->transport = t;
  transport_reference (t);
  for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_RESERVED_MIN; i++)
    TAILQ_INIT (&msg->payload[i]);
  TAILQ_INIT (&msg->post_send);
  LOG_DBG ((LOG_MESSAGE, 90, "message_alloc: allocated %p", msg));
  return msg;
}

/*
 * Allocate a message suitable for a reply to MSG.  Just allocate an empty
 * ISAKMP header as the first segment.
 */
struct message *
message_alloc_reply (struct message *msg)
{
  struct message *reply;

  reply = message_alloc (msg->transport, 0, ISAKMP_HDR_SZ);
  reply->exchange = msg->exchange;
  reply->isakmp_sa = msg->isakmp_sa;
  if (msg->isakmp_sa)
    sa_reference (msg->isakmp_sa);
  return reply;
}

/* Free up all resources used by the MSG message.  */
void
message_free (struct message *msg)
{
  int i;
  struct payload *payload, *next;

  LOG_DBG ((LOG_MESSAGE, 20, "message_free: freeing %p", msg));
  if (!msg)
    return;
  if (msg->orig && msg->orig != (u_int8_t *)msg->iov[0].iov_base)
    free (msg->orig);
  if (msg->iov)
    {
      for (i = 0; i < msg->iovlen; i++)
	if (msg->iov[i].iov_base)
	  free (msg->iov[i].iov_base);
      free (msg->iov);
    }
  if (msg->retrans)
    timer_remove_event (msg->retrans);
  for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_RESERVED_MIN; i++)
    for (payload = TAILQ_FIRST (&msg->payload[i]); payload; payload = next)
      {
	next = TAILQ_NEXT (payload, link);
	free (payload);
      }
  while (TAILQ_FIRST (&msg->post_send) != 0)
    TAILQ_REMOVE (&msg->post_send, TAILQ_FIRST (&msg->post_send), link);

  /* If we are on the send queue, remove us from there.  */
  if (msg->flags & MSG_IN_TRANSIT)
    TAILQ_REMOVE (&msg->transport->sendq, msg, link);
  transport_release (msg->transport);

  if (msg->isakmp_sa)
    sa_release (msg->isakmp_sa);

  free (msg);
}

/*
 * Generic ISAKMP parser.
 * MSG is the ISAKMP message to be parsed.  NEXT is the type of the first
 * payload to be parsed, and it's pointed to by BUF.  ACCEPTED_PAYLOADS
 * tells what payloads are accepted and FUNC is a pointer to a function
 * to be called for each payload found.  Returns the total length of the
 * parsed payloads.
 */
static int
message_parse_payloads (struct message *msg, struct payload *p, u_int8_t next,
			u_int8_t *buf, set *accepted_payloads,
			int (*func) (struct message *, struct payload *,
				     u_int8_t, u_int8_t *))
{
  u_int8_t payload;
  u_int16_t len;
  int sz = 0;

  do
    {
      LOG_DBG ((LOG_MESSAGE, 50,
		"message_parse_payloads: offset 0x%x payload %s",
		buf - (u_int8_t *)msg->iov[0].iov_base,
		constant_name (isakmp_payload_cst, next)));

      /* Does this payload's header fit?  */
      if (buf + ISAKMP_GEN_SZ
	  > (u_int8_t *)msg->iov[0].iov_base + msg->iov[0].iov_len)
	{
	  log_print ("message_parse_payloads: short message");
	  message_drop (msg, ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, 0, 1, 1);
	  return -1;
	}

      /* Ponder on the payload that is at BUF...  */
      payload = next;

      /* Look at the next payload's type.  */
      next = GET_ISAKMP_GEN_NEXT_PAYLOAD (buf);
      if (next >= ISAKMP_PAYLOAD_RESERVED_MIN &&
	  next <= ISAKMP_PAYLOAD_RESERVED_MAX)
	{
	  log_print ("message_parse_payloads: invalid next payload type %d "
		     "in payload of type %d", next, payload);
	  message_drop (msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
	  return -1;
	}

      /* Reserved fields in ISAKMP messages should be zero.  */
      if (GET_ISAKMP_GEN_RESERVED (buf) != 0)
	{
	  log_print ("message_parse_payloads: reserved field non-zero: %x",
		     GET_ISAKMP_GEN_RESERVED (buf));
	  message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
	  return -1;
	}

      /*
       * Decode the payload length field.
       */
      len = GET_ISAKMP_GEN_LENGTH (buf);

      /* Ignore private payloads.  */
      if (next >= ISAKMP_PAYLOAD_PRIVATE_MIN)
	{
	  LOG_DBG ((LOG_MESSAGE, 30,
		    "message_parse_payloads: private next payload type %d "
		    "in payload of type %d ignored", next, payload));
	  goto next_payload;
	}

      /*
       * Check if the current payload is one of the accepted ones at this
       * stage.
       */
      if (!ISSET (payload, accepted_payloads))
	{
	  log_print ("message_parse_payloads: payload type %d unexpected",
		     payload);
	  message_drop (msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
	  return -1;
	}

      /* Call the payload handler specified by the caller.  */
      if (func (msg, p, payload, buf))
	return -1;

    next_payload:
      /* Advance to next payload.  */
      buf += len;
      sz += len;
    }
  while (next != ISAKMP_PAYLOAD_NONE);
  return sz;
}

/*
 * Parse a proposal payload found in message MSG.  PAYLOAD is always
 * ISAKMP_PAYLOAD_PROPOSAL and ignored in here.  It's needed as the API for
 * message_parse_payloads requires it.  BUF points to the proposal's
 * generic payload header.
 */
static int
message_parse_proposal (struct message *msg, struct payload *p,
			u_int8_t payload, u_int8_t *buf)
{
  set payload_set;

  /* Put the proposal into the proposal bucket.  */
  message_index_payload (msg, p, payload, buf);

  ZERO (&payload_set);
  SET (ISAKMP_PAYLOAD_TRANSFORM, &payload_set);
  if (message_parse_payloads (msg,
			      TAILQ_LAST (&msg->payload
					  [ISAKMP_PAYLOAD_PROPOSAL],
					  payload_head),
			      ISAKMP_PAYLOAD_TRANSFORM,
			      buf + ISAKMP_PROP_SPI_OFF
			      + GET_ISAKMP_PROP_SPI_SZ (buf),
			      &payload_set, message_parse_transform) == -1)
    return -1;

  return 0;
}

static int
message_parse_transform (struct message *msg, struct payload *p,
			 u_int8_t payload, u_int8_t *buf)
{
  /* Put the transform into the transform bucket.  */
  message_index_payload (msg, p, payload, buf);

  LOG_DBG ((LOG_MESSAGE, 50, "Transform %d's attributes",
	    GET_ISAKMP_TRANSFORM_NO (buf)));
#ifdef USE_DEBUG
  attribute_map (buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		 GET_ISAKMP_GEN_LENGTH (buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		 msg->exchange->doi->debug_attribute, msg);
#endif

  return 0;
}

/* Validate the certificate payload P in message MSG.  */
static int
message_validate_cert (struct message *msg, struct payload *p)
{
  if (GET_ISAKMP_CERT_ENCODING (p->p) >= ISAKMP_CERTENC_RESERVED_MIN)
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1, 1);
      return -1;
    }
  return 0;
}

/* Validate the certificate request payload P in message MSG.  */
static int
message_validate_cert_req (struct message *msg, struct payload *p)
{
  struct cert_handler *cert;
  size_t len = GET_ISAKMP_GEN_LENGTH (p->p)- ISAKMP_CERTREQ_AUTHORITY_OFF;

  if (GET_ISAKMP_CERTREQ_TYPE (p->p) >= ISAKMP_CERTENC_RESERVED_MIN)
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1, 1);
      return -1;
    }

  /*
   * Check the certificate types we support and if an acceptable authority
   * is included in the payload check if it can be decoded
   */
  cert = cert_get (GET_ISAKMP_CERTREQ_TYPE (p->p));
  if (!cert
      || (len && !cert->certreq_validate (p->p + ISAKMP_CERTREQ_AUTHORITY_OFF,
					  len)))
    {
      message_drop (msg, ISAKMP_NOTIFY_CERT_TYPE_UNSUPPORTED, 0, 1, 1);
      return -1;
    }
  return 0;
}

/*
 * Validate the delete payload P in message MSG.  As a side-effect, create
 * an exchange if we do not have one already.
 */
static int
message_validate_delete (struct message *msg, struct payload *p)
{
  u_int8_t proto = GET_ISAKMP_DELETE_PROTO (p->p);
  struct doi *doi;

  doi = doi_lookup (GET_ISAKMP_DELETE_DOI (p->p));
  if (!doi)
    {
      log_print ("message_validate_delete: DOI not supported");
      message_free (msg);
      return -1;
    }

  /* If we don't have an exchange yet, create one.  */
  if (!msg->exchange)
    {
      if (zero_test (msg->iov[0].iov_base + ISAKMP_HDR_MESSAGE_ID_OFF,
		     ISAKMP_HDR_MESSAGE_ID_LEN))
	msg->exchange = exchange_setup_p1 (msg, doi->id);
      else
	msg->exchange = exchange_setup_p2 (msg, doi->id);
      if (!msg->exchange)
	{
	  log_print ("message_validate_delete: can not create exchange");
	  message_free (msg);
	  return -1;
	}
    }

  if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto (proto))
    {
      log_print ("message_validate_delete: protocol not supported");
      message_free (msg);
      return -1;
    }

  /* Validate the SPIs.  */

  return 0;
}

/*
 * Validate the hash payload P in message MSG.  */
static int
message_validate_hash (struct message *msg, struct payload *p)
{
  /* XXX Not implemented yet.  */
  return 0;
}

/* Validate the identification payload P in message MSG.  */
static int
message_validate_id (struct message *msg, struct payload *p)
{
  struct exchange *exchange = msg->exchange;
  size_t len = GET_ISAKMP_GEN_LENGTH (p->p);

  if (exchange->doi
      && exchange->doi->validate_id_information (GET_ISAKMP_ID_TYPE (p->p),
						 p->p + ISAKMP_ID_DOI_DATA_OFF,
						 p->p + ISAKMP_ID_DATA_OFF,
						 len - ISAKMP_ID_DATA_OFF,
						 exchange))
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION, 0, 1, 1);
      return -1;
    }
  return 0;
}

/* Validate the key exchange payload P in message MSG.  */
static int
message_validate_key_exch (struct message *msg, struct payload *p)
{
  struct exchange *exchange = msg->exchange;
  size_t len = GET_ISAKMP_GEN_LENGTH (p->p);

  if (exchange->doi
      && exchange->doi->validate_key_information (p->p + ISAKMP_KE_DATA_OFF,
						  len - ISAKMP_KE_DATA_OFF))
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_KEY_INFORMATION, 0, 1, 1);
      return -1;
    }
  return 0;
}

/* Validate the nonce payload P in message MSG.  */
static int
message_validate_nonce (struct message *msg, struct payload *p)
{
  /* Nonces require no specific validation.  */
  return 0;
}

/*
 * Validate the notify payload P in message MSG.  As a side-effect, create
 * an exchange if we do not have one already.
 */
static int
message_validate_notify (struct message *msg, struct payload *p)
{
  u_int8_t proto = GET_ISAKMP_NOTIFY_PROTO (p->p);
  u_int16_t type = GET_ISAKMP_NOTIFY_MSG_TYPE (p->p);
  struct doi *doi;

  doi = doi_lookup (GET_ISAKMP_NOTIFY_DOI (p->p));
  if (!doi)
    {
      log_print ("message_validate_notify: DOI not supported");
      message_free (msg);
      return -1;
    }

  /* If we don't have an exchange yet, create one.  */
  if (!msg->exchange)
    {
      if (zero_test (msg->iov[0].iov_base + ISAKMP_HDR_MESSAGE_ID_OFF,
		     ISAKMP_HDR_MESSAGE_ID_LEN))
	msg->exchange = exchange_setup_p1 (msg, doi->id);
      else
	msg->exchange = exchange_setup_p2 (msg, doi->id);
      if (!msg->exchange)
	{
	  log_print ("message_validate_notify: can not create exchange");
	  message_free (msg);
	  return -1;
	}
    }

  if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto (proto))
    {
      log_print ("message_validate_notify: protocol not supported");
      message_free (msg);
      return -1;
    }

  /* XXX Validate the SPI.  */

  if (type < ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE
      || (type >= ISAKMP_NOTIFY_RESERVED_MIN
	  && type < ISAKMP_NOTIFY_PRIVATE_MIN)
      || (type >= ISAKMP_NOTIFY_STATUS_RESERVED1_MIN
	  && type <= ISAKMP_NOTIFY_STATUS_RESERVED1_MAX)
      || (type >= ISAKMP_NOTIFY_STATUS_DOI_MIN
	  && type <= ISAKMP_NOTIFY_STATUS_DOI_MAX
	  && doi->validate_notification (type))
      || type >= ISAKMP_NOTIFY_STATUS_RESERVED2_MIN)
    {
      log_print ("message_validate_notify: message type not supported");
      message_free (msg);
      return -1;
    }
  return 0;
}

/* Validate the proposal payload P in message MSG.  */
static int
message_validate_proposal (struct message *msg, struct payload *p)
{
  u_int8_t proto = GET_ISAKMP_PROP_PROTO (p->p);
  u_int8_t *sa = p->context->p;

  if (proto != ISAKMP_PROTO_ISAKMP
      && msg->exchange->doi->validate_proto (proto))
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_PROTOCOL_ID, 0, 1, 1);
      return -1;
    }

  /* Check that we get monotonically increasing proposal IDs per SA.  */
  if (sa != last_sa)
    last_sa = sa;
  else if (GET_ISAKMP_PROP_NO (p->p) < last_prop_no)
    {
      message_drop (msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1);
      return -1;
    }
  last_prop_no = GET_ISAKMP_PROP_NO (p->p);

  /* XXX Validate the SPI, and other syntactic things.  */

  return 0;
}

/*
 * Validate the SA payload P in message MSG.
 * Aside from normal validation, note what DOI is in use for other
 * validation routines to look at.  Also index the proposal payloads
 * on the fly.
 * XXX This assumes PAYLOAD_SA is always the first payload
 * to be validated, which is true for IKE, except for quick mode where
 * a PAYLOAD_HASH comes first, but in that specific case it does not matter.
 * XXX Make sure the above comment is relevant, isn't SA always checked
 * first due to the IANA assigned payload number?
 */
static int
message_validate_sa (struct message *msg, struct payload *p)
{
  set payload_set;
  size_t len;
  u_int32_t doi_id;
  struct exchange *exchange = msg->exchange;
  u_int8_t *pkt = msg->iov[0].iov_base;

  doi_id = GET_ISAKMP_SA_DOI (p->p);
  if (!doi_lookup (doi_id))
    {
      log_print ("message_validate_sa: DOI not supported");
      message_drop (msg, ISAKMP_NOTIFY_DOI_NOT_SUPPORTED, 0, 1, 1);
      return -1;
    }

  /*
   * It's time to figure out what SA this message is about.  If it is
   * already set, then we are creating a new phase 1 SA.  Otherwise, lookup
   * the SA using the cookies and the message ID.  If we cannot find
   * it, and the phase 1 SA is ready, setup a phase 2 SA.
   */
  if (!exchange)
    {
      if (zero_test (pkt + ISAKMP_HDR_RCOOKIE_OFF, ISAKMP_HDR_RCOOKIE_LEN))
	exchange = exchange_setup_p1 (msg, doi_id);
      else if (msg->isakmp_sa->flags & SA_FLAG_READY)
	exchange = exchange_setup_p2 (msg, doi_id);
      else
	{
	  /* XXX What to do here?  */
	  message_free (msg);
	  return -1;
	}
      if (!exchange)
	{
	  /* XXX Log?  */
	  message_free (msg);
	  return -1;
	}
    }
  msg->exchange = exchange;

  /*
   * Create a struct sa for each SA payload handed to us unless we are the
   * initiator where we only will count them.
   */
  if (exchange->initiator)
    {
      /* XXX Count SA payloads.  */
    }
  else if (sa_create (exchange, msg->transport))
    {
      /* XXX Remove exchange if we just created it?   */
      message_free (msg);
      return -1;
    }

  if (exchange->phase == 1)
    {
      msg->isakmp_sa = TAILQ_FIRST (&exchange->sa_list);
      if (msg->isakmp_sa)
        sa_reference (msg->isakmp_sa);
    }

  /*
   * Let the DOI validate the situation, at the same time it tells us what
   * the length of the situation field is.
   */
  if (exchange->doi->validate_situation (p->p + ISAKMP_SA_SIT_OFF, &len))
    {
      log_print ("message_validate_sa: situation not supported");
      message_drop (msg, ISAKMP_NOTIFY_SITUATION_NOT_SUPPORTED, 0, 1, 1);
      return -1;
    }

  /* Reset the fields we base our proposal & transform number checks on.  */
  last_sa = last_prop = 0;
  last_prop_no = last_xf_no = 0;

  /* Go through the PROPOSAL payloads.  */
  ZERO (&payload_set);
  SET (ISAKMP_PAYLOAD_PROPOSAL, &payload_set);
  if (message_parse_payloads (msg, p, ISAKMP_PAYLOAD_PROPOSAL,
			      p->p + ISAKMP_SA_SIT_OFF + len, &payload_set,
			      message_parse_proposal) == -1)
    return -1;

  return 0;
}

/* Validate the signature payload P in message MSG.  */
static int
message_validate_sig (struct message *msg, struct payload *p)
{
  /* XXX Not implemented yet.  */
  return 0;
}

/* Validate the transform payload P in message MSG.  */
static int
message_validate_transform (struct message *msg, struct payload *p)
{
  u_int8_t proto = GET_ISAKMP_PROP_PROTO (p->context->p);
  u_int8_t *prop = p->context->p;

  if (msg->exchange->doi
      ->validate_transform_id (proto, GET_ISAKMP_TRANSFORM_ID (p->p)))
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_TRANSFORM_ID, 0, 1, 1);
      return -1;
    }

  /* Check that the reserved field is zero.  */
  if (!zero_test (p->p + ISAKMP_TRANSFORM_RESERVED_OFF,
		  ISAKMP_TRANSFORM_RESERVED_LEN))
    {
      message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
      return -1;
    }

  /*
   * Check that we get monotonically increasing transform numbers per proposal.
   */
  if (prop != last_prop)
    last_prop = prop;
  else if (GET_ISAKMP_TRANSFORM_NO (p->p) <= last_xf_no)
    {
      message_drop (msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1);
      return -1;
    }
  last_xf_no = GET_ISAKMP_TRANSFORM_NO (p->p);

  /* Validate the attributes.  */
  if (attribute_map (p->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		     GET_ISAKMP_GEN_LENGTH (p->p)
		     - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		     msg->exchange->doi->validate_attribute, msg))
    {
      message_drop (msg, ISAKMP_NOTIFY_ATTRIBUTES_NOT_SUPPORTED, 0, 1, 1);
      return -1;
    }

  return 0;
}

/* Validate the vendor payload P in message MSG.  */
static int
message_validate_vendor (struct message *msg, struct payload *p)
{
  /* Vendor IDs are only allowed in phase 1.  */
  if (msg->exchange->phase != 1)
    {
      message_drop (msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
      return -1;
    }

  LOG_DBG ((LOG_MESSAGE, 40, "message_validate_vendor: vendor ID seen"));
  return 0;
}

/*
 * Add an index-record pointing to the payload at BUF in message MSG
 * to the PAYLOAD bucket of payloads.  This allows us to quickly reference
 * payloads by type.  Also stash the parent payload P link into the new
 * node so we can go from transforms -> payloads -> SAs.
 */
static int
message_index_payload (struct message *msg, struct payload *p,
		       u_int8_t payload, u_int8_t *buf)
{
  struct payload *payload_node;

  /* Put the payload pointer into the right bucket.  */
  payload_node = malloc (sizeof *payload_node);
  if (!payload_node)
    return -1;
  payload_node->p = buf;
  payload_node->context = p;
  payload_node->flags = 0;
  TAILQ_INSERT_TAIL (&msg->payload[payload], payload_node, link);
  return 0;
}

/*
 * Group each payload found in MSG by type for easy reference later.
 * While doing this, validate the generic parts of the message structure too.
 * NEXT is the 1st payload's type.  This routine will also register the
 * computed message length (i.e. without padding) in msg->iov[0].iov_len.
 */
static int
message_sort_payloads (struct message *msg, u_int8_t next)
{
  set payload_set;
  int i, sz;

  ZERO (&payload_set);
  for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_RESERVED_MIN; i++)
    if (i != ISAKMP_PAYLOAD_PROPOSAL && i != ISAKMP_PAYLOAD_TRANSFORM)
      SET (i, &payload_set);
  sz = message_parse_payloads (msg, 0, next,
			       msg->iov[0].iov_base + ISAKMP_HDR_SZ,
			       &payload_set, message_index_payload);
  if (sz == -1)
    return -1;
  msg->iov[0].iov_len = ISAKMP_HDR_SZ + sz;
  SET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz);
  return 0;
}

/* Run all the generic payload tests that the drafts specify.  */
static int
message_validate_payloads (struct message *msg)
{
  int i;
  struct payload *p;

  for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_RESERVED_MIN; i++)
    for (p = TAILQ_FIRST (&msg->payload[i]); p; p = TAILQ_NEXT (p, link))
      {
	LOG_DBG ((LOG_MESSAGE, 60,
		  "message_validate_payloads: "
		  "payload %s at %p of message %p",
		  constant_name (isakmp_payload_cst, i), p->p, msg));
	field_dump_payload (fields[i - ISAKMP_PAYLOAD_SA], p->p);
	if (message_validate_payload[i - ISAKMP_PAYLOAD_SA] (msg, p))
	  return -1;
      }
  return 0;
}

/*
 * All incoming messages go through here.  We do generic validity checks
 * and try to find or establish SAs.  Last but not least we try to find
 * the exchange this message, MSG, is part of, and feed it there.
 */
int
message_recv (struct message *msg)
{
  u_int8_t *buf = msg->iov[0].iov_base;
  size_t sz = msg->iov[0].iov_len;
  u_int8_t exch_type;
  int setup_isakmp_sa, msgid_is_zero;
  u_int8_t flags;
  struct keystate *ks = 0;
  struct proto tmp_proto;
  struct sa tmp_sa;

  /* Possibly dump a raw hex image of the message to the log channel.  */
  message_dump_raw ("message_recv", msg, LOG_MESSAGE);

  /* Messages shorter than an ISAKMP header are bad.  */
  if (sz < ISAKMP_HDR_SZ || sz != GET_ISAKMP_HDR_LENGTH (buf))
    {
      log_print ("message_recv: bad message length");
      message_drop (msg, ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, 0, 1, 1);
      return -1;
    }

  /*
   * If the responder cookie is zero, this is a request to setup an ISAKMP SA.
   * Otherwise the cookies should refer to an existing ISAKMP SA.
   *
   * XXX This is getting ugly, please reread later to see if it can be made
   * nicer.
   */
  setup_isakmp_sa = zero_test (buf + ISAKMP_HDR_RCOOKIE_OFF,
			       ISAKMP_HDR_RCOOKIE_LEN);
  if (setup_isakmp_sa)
    {
      /*
       * This might be a retransmission of a former ISAKMP SA setup message.
       * If so, just drop it.
       * XXX Must we really look in both the SA and exchange pools?
       */
      if (exchange_lookup_from_icookie (buf + ISAKMP_HDR_ICOOKIE_OFF)
	  || sa_lookup_from_icookie (buf + ISAKMP_HDR_ICOOKIE_OFF))
	{
	  /*
	   * XXX Later we should differentiate between retransmissions and
	   * potential replay attacks.
	   */
	  LOG_DBG ((LOG_MESSAGE, 90,
		    "message_recv: dropping setup for existing SA"));
	  message_free (msg);
	  return -1;
	}
    }
  else
    {
      msg->isakmp_sa = sa_lookup_by_header (buf, 0);
      if (msg->isakmp_sa)
        sa_reference (msg->isakmp_sa);

      /*
       * If we cannot find an ISAKMP SA out of the cookies, this is either
       * a responder's first reply, and we need to upgrade our exchange,
       * or it's just plain invalid cookies.
       */
      if (!msg->isakmp_sa)
	{
	  msg->exchange
	    = exchange_lookup_from_icookie (buf + ISAKMP_HDR_ICOOKIE_OFF);
	  if (msg->exchange && msg->exchange->phase == 1
	      && zero_test (msg->exchange->cookies + ISAKMP_HDR_RCOOKIE_OFF,
			    ISAKMP_HDR_RCOOKIE_LEN))
	    exchange_upgrade_p1 (msg);
	  else
	    {
	      log_print ("message_recv: invalid cookie(s) %08x%08x %08x%08x",
			 decode_32 (buf + ISAKMP_HDR_ICOOKIE_OFF),
			 decode_32 (buf + ISAKMP_HDR_ICOOKIE_OFF + 4),
			 decode_32 (buf + ISAKMP_HDR_RCOOKIE_OFF),
			 decode_32 (buf + ISAKMP_HDR_RCOOKIE_OFF + 4));
	      tmp_proto.sa = &tmp_sa;
	      tmp_sa.doi = doi_lookup (ISAKMP_DOI_ISAKMP);
	      tmp_proto.proto = ISAKMP_PROTO_ISAKMP;
	      tmp_proto.spi_sz[1] = ISAKMP_HDR_COOKIES_LEN;
	      tmp_proto.spi[1] = buf + ISAKMP_HDR_COOKIES_OFF;
	      message_drop (msg, ISAKMP_NOTIFY_INVALID_COOKIE, &tmp_proto, 1,
			    1);
	      return -1;
	    }
#if 0
	  msg->isakmp_sa
	    = sa_lookup_from_icookie (buf + ISAKMP_HDR_ICOOKIE_OFF);
	  if (msg->isakmp_sa)
	    sa_isakmp_upgrade (msg);
#endif
	}
      msg->exchange = exchange_lookup (buf, 1);
    }

  if (message_check_duplicate (msg))
    return -1;

  if (GET_ISAKMP_HDR_NEXT_PAYLOAD (buf) >= ISAKMP_PAYLOAD_RESERVED_MIN)
    {
      log_print ("message_recv: "
		 "invalid payload type %d in ISAKMP header "
		 "(check passphrases, if applicable and in Phase 1)",
		 GET_ISAKMP_HDR_NEXT_PAYLOAD (buf));
      message_drop (msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
      return -1;
    }

  /* Validate that the message is of version 1.0.  */
  if (ISAKMP_VERSION_MAJOR (GET_ISAKMP_HDR_VERSION (buf)) != 1)
    {
      log_print ("message_recv: invalid version major %d",
		 ISAKMP_VERSION_MAJOR (GET_ISAKMP_HDR_VERSION (buf)));
      message_drop (msg, ISAKMP_NOTIFY_INVALID_MAJOR_VERSION, 0, 1, 1);
      return -1;
    }

  if (ISAKMP_VERSION_MINOR (GET_ISAKMP_HDR_VERSION (buf)) != 0)
    {
      log_print ("message_recv: invalid version minor %d",
		 ISAKMP_VERSION_MINOR (GET_ISAKMP_HDR_VERSION (buf)));
      message_drop (msg, ISAKMP_NOTIFY_INVALID_MINOR_VERSION, 0, 1, 1);
      return -1;
    }

  /*
   * Validate the exchange type.  If it's a DOI-specified exchange wait until
   * after all payloads have been seen for the validation as the SA payload
   * might not yet have been parsed, thus the DOI might be unknown.
   */
  exch_type = GET_ISAKMP_HDR_EXCH_TYPE (buf);
  if (exch_type == ISAKMP_EXCH_NONE
      || (exch_type >= ISAKMP_EXCH_FUTURE_MIN &&
	  exch_type <= ISAKMP_EXCH_FUTURE_MAX)
      || (setup_isakmp_sa && exch_type >= ISAKMP_EXCH_DOI_MIN))
    {
      log_print ("message_recv: invalid exchange type %s",
		 constant_name (isakmp_exch_cst, exch_type));
      message_drop (msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1, 1);
      return -1;
    }

  /*
   * Check for unrecognized flags, or the encryption flag when we don't
   * have an ISAKMP SA to decrypt with.
   */
  flags = GET_ISAKMP_HDR_FLAGS (buf);
  if (flags
      & ~(ISAKMP_FLAGS_ENC | ISAKMP_FLAGS_COMMIT | ISAKMP_FLAGS_AUTH_ONLY))
    {
      log_print ("message_recv: invalid flags 0x%x",
		 GET_ISAKMP_HDR_FLAGS (buf));
      message_drop (msg, ISAKMP_NOTIFY_INVALID_FLAGS, 0, 1, 1);
      return -1;
    }

  /* If we are about to setup an ISAKMP SA, the message ID must be zero.  */
  msgid_is_zero = zero_test (buf + ISAKMP_HDR_MESSAGE_ID_OFF,
			     ISAKMP_HDR_MESSAGE_ID_LEN);
  if (setup_isakmp_sa && !msgid_is_zero)
    {
      log_print ("message_recv: invalid message id");
      message_drop (msg, ISAKMP_NOTIFY_INVALID_MESSAGE_ID, 0, 1, 1);
      return -1;
    }

  if (!setup_isakmp_sa && msgid_is_zero)
    {
      /*
       * XXX Very likely redundant, look at the  else clause of the
       * if (setup_isakmp_sa) statement above.
       */
      msg->exchange = exchange_lookup (buf, 0);
      if (!msg->exchange)
	{
	  log_print ("message_recv: phase 1 message after ISAKMP SA is ready");
	  message_free (msg);
	  return -1;
	}
      else if (msg->exchange->last_sent)
	{
	  LOG_DBG ((LOG_MESSAGE, 80,
		    "message_recv: resending last message from phase 1"));
	  message_send (msg->exchange->last_sent);
	}
    }

  if (flags & ISAKMP_FLAGS_ENC)
    {
      if (msg->isakmp_sa == NULL)
	{
	  LOG_DBG ((LOG_MISC, 10,
		    "message_recv: no isakmp_sa for encrypted message"));
	  return -1;
	}

      /* Decrypt rest of message using a DOI-specified IV.  */
      ks = msg->isakmp_sa->doi->get_keystate (msg);
      if (!ks)
	{
	  message_free (msg);
	  return -1;
	}
      msg->orig = malloc (sz);
      if (!msg->orig)
	{
	  message_free (msg);
	  free (ks);
	  return -1;
	}
      memcpy (msg->orig, buf, sz);
      crypto_decrypt (ks, buf + ISAKMP_HDR_SZ, sz - ISAKMP_HDR_SZ);
    }
  else
    msg->orig = buf;
  msg->orig_sz = sz;

  /* IKE packet capture */
  message_packet_log (msg);

  /*
   * Check the overall payload structure at the same time as indexing them by
   * type.
   */
  if (GET_ISAKMP_HDR_NEXT_PAYLOAD (buf) != ISAKMP_PAYLOAD_NONE
      && message_sort_payloads (msg, GET_ISAKMP_HDR_NEXT_PAYLOAD (buf)))
    {
      if (ks)
	free (ks);
      return -1;
    }

  /*
   * Run generic payload tests now.  If anything fails these checks, the
   * message needs either to be retained for later duplicate checks or
   * freed entirely.
   * XXX Should SAs and even transports be cleaned up then too?
   */
  if (message_validate_payloads (msg))
    {
      if (ks)
	free (ks);
      return -1;
    }

  /* If we have not found an exchange by now something is definitely wrong.  */
  if (!msg->exchange)
    {
      log_print ("message_recv: no exchange");
      message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
      if (ks)
	free (ks);
      return -1;
    }

  /*
   * Now we can validate DOI-specific exchange types.  If we have no SA
   * DOI-specific exchange types are definitely wrong.
   */
  if (exch_type >= ISAKMP_EXCH_DOI_MIN && exch_type <= ISAKMP_EXCH_DOI_MAX
      && msg->exchange->doi->validate_exchange (exch_type))
    {
      log_print ("message_recv: invalid DOI exchange type %d", exch_type);
      message_drop (msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1, 1);
      if (ks)
	free (ks);
      return -1;
    }

  /* Make sure the IV we used gets saved in the proper SA.  */
  if (ks)
    {
      if (!msg->exchange->keystate)
	{
	  msg->exchange->keystate = ks;
	  msg->exchange->crypto = ks->xf;
	}
      else
	free (ks);
    }

  /* Handle the flags.  */
  if (flags & ISAKMP_FLAGS_ENC)
    msg->exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
  if ((msg->exchange->flags & EXCHANGE_FLAG_COMMITTED) == 0
      && (flags & ISAKMP_FLAGS_COMMIT))
    msg->exchange->flags |= EXCHANGE_FLAG_HE_COMMITTED;

  /* OK let the exchange logic do the rest.  */
  exchange_run (msg);

  return 0;
}

void
message_send_expire (struct message *msg)
{
  msg->retrans = 0;

  message_send (msg);
}

/* Queue up message MSG for transmittal.  */
void
message_send (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  struct message *m;

  /* Remove retransmissions on this message  */
  if (msg->retrans)
    {
      timer_remove_event (msg->retrans);
      msg->retrans = 0;
    }

  /* IKE packet capture */
  message_packet_log (msg);

  /*
   * If the ISAKMP SA has set up encryption, encrypt the message.
   * However, in a retransmit, it is already encrypted.
   */
  if ((msg->flags & MSG_ENCRYPTED) == 0
      && exchange->flags & EXCHANGE_FLAG_ENCRYPT)
    {
      if (!exchange->keystate)
	{
	  exchange->keystate = exchange->doi->get_keystate (msg);
	  exchange->crypto = exchange->keystate->xf;
	  exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
	}

      if (message_encrypt (msg))
	{
	  /* XXX Log.  */
	  return;
	}
    }

  /* Keep the COMMIT bit on.  */
  if (exchange->flags & EXCHANGE_FLAG_COMMITTED)
    SET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base,
			  GET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base)
			  | ISAKMP_FLAGS_COMMIT);

  message_dump_raw ("message_send", msg, LOG_MESSAGE);
  msg->flags |= MSG_IN_TRANSIT;
  exchange->in_transit = msg;
  
  /*
   * If we get a retransmission of a message before our response
   * has left the queue, don't queue it again, as it will result
   * in a circular list.
   */
  for (m = TAILQ_FIRST (&msg->transport->sendq); m; m = TAILQ_NEXT (m, link))
    if (m == msg)
      {
	LOG_DBG ((LOG_MESSAGE, 60, "message_send: msg %p already on sendq", 
		  m));
	return;
      }

  TAILQ_INSERT_TAIL (&msg->transport->sendq, msg, link);
}

/*
 * Setup the ISAKMP message header for message MSG.  EXCHANGE is the exchange
 * type, FLAGS are the ISAKMP header flags and MSG_ID is message ID
 * identifying the exchange.
 */
void
message_setup_header (struct message *msg, u_int8_t exchange, u_int8_t flags,
		      u_int8_t *msg_id)
{
  u_int8_t *buf = msg->iov[0].iov_base;

  SET_ISAKMP_HDR_ICOOKIE (buf, msg->exchange->cookies);
  SET_ISAKMP_HDR_RCOOKIE (buf,
			  msg->exchange->cookies + ISAKMP_HDR_ICOOKIE_LEN);
  SET_ISAKMP_HDR_NEXT_PAYLOAD (buf, ISAKMP_PAYLOAD_NONE);
  SET_ISAKMP_HDR_VERSION (buf, ISAKMP_VERSION_MAKE (1, 0));
  SET_ISAKMP_HDR_EXCH_TYPE (buf, exchange);
  SET_ISAKMP_HDR_FLAGS (buf, flags);
  SET_ISAKMP_HDR_MESSAGE_ID (buf, msg_id);
  SET_ISAKMP_HDR_LENGTH (buf, msg->iov[0].iov_len);
}

/*
 * Add the payload of type PAYLOAD in BUF sized SZ to the MSG message.
 * The caller thereby is released from the responsibility of freeing BUF,
 * unless we return a failure of course.  If LINK is set the former
 * payload's "next payload" field to PAYLOAD.
 *
 * XXX We might want to resize the iov array several slots at a time.
 */
int
message_add_payload (struct message *msg, u_int8_t payload, u_int8_t *buf,
		     size_t sz, int link)
{
  struct iovec *new_iov;
  struct payload *payload_node;

  payload_node = malloc (sizeof *payload_node);
  if (!payload_node)
    {
      log_error ("message_add_payload: malloc (%d) failed",
		 sizeof *payload_node);
      return -1;
    }
  new_iov
    = (struct iovec *)realloc (msg->iov, (msg->iovlen + 1) * sizeof *msg->iov);
  if (!new_iov)
    {
      log_error ("message_add_payload: realloc (%p, %d) failed", msg->iov,
		 (msg->iovlen + 1) * sizeof *msg->iov);
      free (payload_node);
      return -1;
    }
  msg->iov = new_iov;
  new_iov[msg->iovlen].iov_base = buf;
  new_iov[msg->iovlen].iov_len = sz;
  msg->iovlen++;
  if (link)
    *msg->nextp = payload;
  msg->nextp = buf + ISAKMP_GEN_NEXT_PAYLOAD_OFF;
  *msg->nextp = ISAKMP_PAYLOAD_NONE;
  SET_ISAKMP_GEN_RESERVED (buf, 0);
  SET_ISAKMP_GEN_LENGTH (buf, sz);
  SET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base,
			 GET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base) + sz);

  /*
   * For the sake of exchange_validate we index the payloads even in outgoing
   * messages, however context and flags are uninteresting in this situation.
   */
  payload_node->p = buf;
  TAILQ_INSERT_TAIL (&msg->payload[payload], payload_node, link);
  return 0;
}

/* XXX Move up when ready.  */
struct info_args {
  char discr;
  u_int32_t doi;
  u_int8_t proto;
  u_int16_t spi_sz;
  union {
    struct {
      u_int16_t msg_type;
      u_int8_t *spi;
    } n;
    struct {
      u_int16_t nspis;
      u_int8_t *spis;
    } d;
  } u;
};

/*
 * As a reaction to the incoming message MSG create an informational exchange
 * protected by ISAKMP_SA and send a notify payload of type NOTIFY, with
 * fields initialized from SA.  INCOMING is true if the SPI field should be
 * filled with the incoming SPI and false if it is to be filled with the
 * outgoing one.
 *
 * XXX Should we handle sending multiple notify payloads?  The draft allows
 * it, but do we need it?  Furthermore, should we not return a success
 * status value?
 */
void
message_send_notification (struct message *msg, struct sa *isakmp_sa,
			   u_int16_t notify, struct proto *proto,
			   int incoming)
{
  struct info_args args;
  struct sa *doi_sa = proto ? proto->sa : isakmp_sa;

  args.discr = 'N';
  args.doi = doi_sa ? doi_sa->doi->id : ISAKMP_DOI_ISAKMP;
  args.proto = proto ? proto->proto : ISAKMP_PROTO_ISAKMP;
  args.spi_sz = proto ? proto->spi_sz[incoming] : 0;
  args.u.n.msg_type = notify;
  args.u.n.spi = proto ? proto->spi[incoming] : 0;
  if (isakmp_sa && (isakmp_sa->flags & SA_FLAG_READY))
    exchange_establish_p2 (isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, 0 ,0);
  else
    exchange_establish_p1 (msg->transport, ISAKMP_EXCH_INFO,
			   msg->exchange
			   ? msg->exchange->doi->id : ISAKMP_DOI_ISAKMP,
			   0, &args, 0, 0);
}

/* Send a DELETE inside an informational exchange for each protocol in SA.  */
void
message_send_delete (struct sa *sa)
{
  struct info_args args;
  struct proto *proto;
  struct sa *isakmp_sa;
  struct sockaddr *dst;
  socklen_t dstlen;

  sa->transport->vtbl->get_dst (sa->transport, &dst, &dstlen);
  isakmp_sa = sa_isakmp_lookup_by_peer (dst, dstlen);
  if (!isakmp_sa)
    {
      /*
       * XXX We ought to setup an ISAKMP SA with our peer here and send
       * the DELETE over that one.
       */
      return;
    }

  args.discr = 'D';
  args.doi = sa->doi->id;
  args.u.d.nspis = 1;
  for (proto = TAILQ_FIRST (&sa->protos); proto;
       proto = TAILQ_NEXT (proto, link))
    {
      args.proto = proto->proto;
      args.spi_sz = proto->spi_sz[1];
      args.u.d.spis = proto->spi[1];
      exchange_establish_p2 (isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, 0 ,0);
    }
}

/* Build the informational message into MSG.  */
int
message_send_info (struct message *msg)
{
  u_int8_t *buf;
  size_t sz;
  struct info_args *args = msg->extra;
  u_int8_t payload;

  /* Let the DOI get the first hand on the message.  */
  if (msg->exchange->doi->informational_pre_hook)
    if (msg->exchange->doi->informational_pre_hook (msg))
      return -1;

  sz = (args->discr == 'N' ? ISAKMP_NOTIFY_SPI_OFF + args->spi_sz
	: ISAKMP_DELETE_SPI_OFF + args->u.d.nspis * args->spi_sz);
  buf = calloc (1, sz);
  if (!buf)
    {
      log_error ("message_send_info: calloc (1, %d) failed", sz);
      message_free (msg);
      return -1;
    }

  switch (args->discr)
    {
    case 'N':
      /* Build the NOTIFY payload.  */
      payload = ISAKMP_PAYLOAD_NOTIFY;
      SET_ISAKMP_NOTIFY_DOI (buf, args->doi);
      SET_ISAKMP_NOTIFY_PROTO (buf, args->proto);
      SET_ISAKMP_NOTIFY_SPI_SZ (buf, args->spi_sz);
      SET_ISAKMP_NOTIFY_MSG_TYPE (buf, args->u.n.msg_type);
      memcpy (buf + ISAKMP_NOTIFY_SPI_OFF, args->u.n.spi, args->spi_sz);
      break;

    case 'D':
    default:			/* Silence GCC.  */
      /* Build the DELETE payload.  */
      payload = ISAKMP_PAYLOAD_DELETE;
      SET_ISAKMP_DELETE_DOI (buf, args->doi);
      SET_ISAKMP_DELETE_PROTO (buf, args->proto);
      SET_ISAKMP_DELETE_SPI_SZ (buf, args->spi_sz);
      SET_ISAKMP_DELETE_NSPIS (buf, args->u.d.nspis);
      memcpy (buf + ISAKMP_DELETE_SPI_OFF, args->u.d.spis,
	      args->u.d.nspis * args->spi_sz);
      break;
    }

  if (message_add_payload (msg, payload, buf, sz, 1))
    {
      free (buf);
      message_free (msg);
      return -1;
    }

  /* Let the DOI get the last hand on the message.  */
  if (msg->exchange->doi->informational_post_hook)
    if (msg->exchange->doi->informational_post_hook (msg))
      {
	message_free (msg);
	return -1;
      }

  return 0;
}

/*
 * Drop the MSG message due to reason given in NOTIFY.  If NOTIFY is set
 * send out a notification to the originator.  Fill this notification with
 * values from PROTO.  INCOMING decides which SPI to include.  If CLEAN is
 * set, free the message when ready with it.
 */
void
message_drop (struct message *msg, int notify, struct proto *proto,
	      int incoming, int clean)
{
  struct transport *t = msg->transport;
  struct sockaddr *dst;
  int dst_len;

  t->vtbl->get_dst (t, &dst, &dst_len);

  /* XXX Assumes IPv4.  */
  log_print ("dropped message from %s port %d due to notification type %s",
	     inet_ntoa (((struct sockaddr_in *)dst)->sin_addr),
	     ntohs (((struct sockaddr_in *)dst)->sin_port),
	     constant_name (isakmp_notify_cst, notify));

  /* If specified, return a notification.  */
  if (notify)
    message_send_notification (msg, msg->isakmp_sa, notify, proto, incoming);
  if (clean)
    message_free (msg);
}

/*
 * If the user demands debug printouts, printout MSG with as much detail
 * as we can without resorting to per-payload handling.
 */
void
message_dump_raw (char *header, struct message *msg, int class)
{
  int i, j, k = 0;
  char buf[80], *p = buf;

  LOG_DBG ((class, 70, "%s: message %p", header, msg));
  field_dump_payload (isakmp_hdr_fld, msg->iov[0].iov_base);
  for (i = 0; i < msg->iovlen; i++)
    for (j = 0; j < msg->iov[i].iov_len; j++)
      {
	sprintf (p, "%02x", ((u_int8_t *)msg->iov[i].iov_base)[j]);
	p += 2;
	if (++k % 32 == 0)
	  {
	    *p = '\0';
	    LOG_DBG ((class, 70, "%s: %s", header, buf));
	    p = buf;
	  }
	else if (k % 4 == 0)
	  *p++ = ' ';
      }
  *p = '\0';
  if (p != buf)
    LOG_DBG ((class, 70, "%s: %s", header, buf));
}

static void
message_packet_log (struct message *msg)
{
#ifdef USE_DEBUG
  struct sockaddr *src, *dst;
  int srclen, dstlen;

  /* Don't log retransmissions. Redundant for incoming packets... */
  if (msg->xmits > 0)
    return;

  /* Figure out direction. */
  if (msg->exchange && msg->exchange->initiator ^ (msg->exchange->step % 2))
    {
      msg->transport->vtbl->get_src (msg->transport, &src, &srclen);
      msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);
    }
  else
    {
      msg->transport->vtbl->get_src (msg->transport, &dst, &dstlen);
      msg->transport->vtbl->get_dst (msg->transport, &src, &srclen);
    }

  log_packet_iov (src, dst, msg->iov, msg->iovlen);
#endif /* USE_DEBUG */
}

/*
 * Encrypt an outgoing message MSG.  As outgoing messages are represented
 * with an iovec with one segment per payload, we need to coalesce them
 * into just une buffer containing all payloads and some padding before
 * we encrypt.
 */
static int
message_encrypt (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  size_t sz = 0;
  u_int8_t *buf;
  int i;

  /* If no payloads, nothing to do.  */
  if (msg->iovlen == 1)
    return 0;

  /*
   * For encryption we need to put all payloads together in a single buffer.
   * This buffer should be padded to the current crypto transform's blocksize.
   */
  for (i = 1; i < msg->iovlen; i++)
    sz += msg->iov[i].iov_len;
  sz = ((sz + exchange->crypto->blocksize - 1) / exchange->crypto->blocksize)
    * exchange->crypto->blocksize;
  buf = realloc (msg->iov[1].iov_base, sz);
  if (!buf)
    {
      log_error ("message_encrypt: realloc (%p, %d) failed",
		 msg->iov[1].iov_base, sz);
      return -1;
    }
  msg->iov[1].iov_base = buf;
  for (i = 2; i < msg->iovlen; i++)
    {
      memcpy (buf + msg->iov[1].iov_len, msg->iov[i].iov_base,
	      msg->iov[i].iov_len);
      msg->iov[1].iov_len += msg->iov[i].iov_len;
      free (msg->iov[i].iov_base);
    }

  /* Pad with zeroes.  */
  memset (buf + msg->iov[1].iov_len, '\0', sz - msg->iov[1].iov_len);
  msg->iov[1].iov_len = sz;
  msg->iovlen = 2;

  SET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base,
			GET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base)
			| ISAKMP_FLAGS_ENC);
  SET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz);
  crypto_encrypt (exchange->keystate, buf, msg->iov[1].iov_len);
  msg->flags |= MSG_ENCRYPTED;

  /* Update the IV so we can decrypt the next incoming message.  */
  crypto_update_iv (exchange->keystate);

  return 0;
}

/*
 * Check whether the message MSG is a duplicate of the last one negotiating
 * this specific SA.
 */
static int
message_check_duplicate (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  size_t sz = msg->iov[0].iov_len;
  u_int8_t *pkt = msg->iov[0].iov_base;

  /* If no SA has been found, we cannot test, thus it's good.  */
  if (!exchange)
    return 0;

  LOG_DBG ((LOG_MESSAGE, 90, "message_check_duplicate: last_received 0x%x",
	    exchange->last_received));
  if (exchange->last_received)
    {
      LOG_DBG_BUF ((LOG_MESSAGE, 95,
		    "message_check_duplicate: last_received",
		    exchange->last_received->orig,
		    exchange->last_received->orig_sz));
      /* Is it a duplicate, lose the new one.  */
      if (sz == exchange->last_received->orig_sz
	  && memcmp (pkt, exchange->last_received->orig, sz) == 0)
	{
	  LOG_DBG ((LOG_MESSAGE, 80,
		    "message_check_duplicate: dropping dup"));

	  /*
	   * Retransmit if the previos sent message was the last of an
	   * exchange, otherwise just wait for the ordinary retransmission.
	   */
	  if (exchange->last_sent && (exchange->last_sent->flags & MSG_LAST))
	    message_send (exchange->last_sent);
	  message_free (msg);
	  return -1;
	}
    }

  /*
   * As this new message is an indication that state is moving forward
   * at the peer, remove the retransmit timer on our last message.
   */
  if (exchange->last_sent)
    {
      if (exchange->last_sent == exchange->in_transit)
	{
	  TAILQ_REMOVE (&exchange->in_transit->transport->sendq,
			exchange->in_transit, link);
	  exchange->in_transit = 0;
	}
      message_free (exchange->last_sent);
      exchange->last_sent = 0;
    }

  return 0;
}

/* Helper to message_negotiate_sa.  */
static INLINE struct payload *
step_transform (struct payload *tp, struct payload **propp,
		struct payload **sap)
{
  tp = TAILQ_NEXT (tp, link);
  if (tp)
    {
      *propp = tp->context;
      *sap = (*propp)->context;
    }
  return tp;
}

/*
 * Pick out the first transforms out of MSG (which should contain at least one
 * SA payload) we accept as a full protection suite.
 */
int
message_negotiate_sa (struct message *msg,
		      int (*validate) (struct exchange *, struct sa *,
				       struct sa *))
{
  struct payload *tp, *propp, *sap, *next_tp = 0, *next_propp, *next_sap;
  struct payload *saved_tp = 0, *saved_propp = 0, *saved_sap = 0;
  struct sa *sa;
  struct proto *proto;
  int suite_ok_so_far = 0;
  struct exchange *exchange = msg->exchange;

  /*
   * This algorithm is a weird bottom-up thing... mostly due to the
   * payload links pointing upwards.
   *
   * The algorithm goes something like this:
   * Foreach transform
   *   If transform is compatible
   *     Remember that this protocol can work
   *     Skip to last transform of this protocol
   *   If next transform belongs to a new protocol inside the same suite
   *     If no transform was found for the current protocol
   *       Forget all earlier transforms for protocols in this suite
   *       Skip to last transform of this suite
   *   If next transform belongs to a new suite
   *     If the current protocol had an OK transform
   *       Skip to the last transform of this SA
   *   If the next transform belongs to a new SA
   *     If no transforms have been chosen
   *       Issue a NO_PROPOSAL_CHOSEN notification
   */

  sa = TAILQ_FIRST (&exchange->sa_list);
  for (tp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]); tp;
       tp = next_tp)
    {
      propp = tp->context;
      sap = propp->context;
      sap->flags |= PL_MARK;
      next_tp = step_transform (tp, &next_propp, &next_sap);

      /* For each transform, see if it is compatible.  */
      if (!attribute_map (tp->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
			  GET_ISAKMP_GEN_LENGTH (tp->p)
			  - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
			  exchange->doi->is_attribute_incompatible, msg))
	{
	  LOG_DBG ((LOG_NEGOTIATION, 30,
		    "message_negotiate_sa: "
		    "transform %d proto %d proposal %d ok",
		    GET_ISAKMP_TRANSFORM_NO (tp->p),
		    GET_ISAKMP_PROP_PROTO (propp->p),
		    GET_ISAKMP_PROP_NO (propp->p)));
	  if (sa_add_transform (sa, tp, exchange->initiator, &proto))
	    goto cleanup;
	  suite_ok_so_far = 1;

	  saved_tp = next_tp;
	  saved_propp = next_propp;
	  saved_sap = next_sap;
	  /* Skip to last transform of this protocol proposal.  */
	  while ((next_tp = step_transform (tp, &next_propp, &next_sap))
		 && next_propp == propp)
	    tp = next_tp;
	}

    retry_transform:
      /*
       * Figure out if we will be looking at a new protocol proposal
       * inside the current protection suite.
       */
      if (next_tp && propp != next_propp && sap == next_sap
	  && (GET_ISAKMP_PROP_NO (propp->p)
	      == GET_ISAKMP_PROP_NO (next_propp->p)))
	{
	  if (!suite_ok_so_far)
	    {
	      LOG_DBG ((LOG_NEGOTIATION, 30,
			"message_negotiate_sa: proto %d proposal %d failed",
			GET_ISAKMP_PROP_PROTO (propp->p),
			GET_ISAKMP_PROP_NO (propp->p)));
	      /* Remove potentially succeeded choices from the SA.  */
	      while (TAILQ_FIRST (&sa->protos))
		TAILQ_REMOVE (&sa->protos, TAILQ_FIRST (&sa->protos), link);

	      /* Skip to the last transform of this protection suite.  */
	      while ((next_tp = step_transform (tp, &next_propp, &next_sap))
		     && (GET_ISAKMP_PROP_NO (next_propp->p)
			 == GET_ISAKMP_PROP_NO (propp->p))
		     && next_sap == sap)
		tp = next_tp;
	    }
	  suite_ok_so_far = 0;
	}

      /* Figure out if we will be looking at a new protection suite.  */
      if (!next_tp
	  || (propp != next_propp
	      && (GET_ISAKMP_PROP_NO (propp->p)
		  != GET_ISAKMP_PROP_NO (next_propp->p)))
	  || sap != next_sap)
	{
	  /*
	   * Check if the suite we just considered was OK, if so we check
	   * it against the accepted ones.
	   */
	  if (suite_ok_so_far)
	    {
	      if (!validate || validate (exchange, sa, msg->isakmp_sa))
		{
		  LOG_DBG ((LOG_NEGOTIATION, 30,
			    "message_negotiate_sa: proposal %d succeeded",
			    GET_ISAKMP_PROP_NO (propp->p)));

		  /* Skip to the last transform of this SA.  */
		  while ((next_tp
			  = step_transform (tp, &next_propp, &next_sap))
			 && next_sap == sap)
		    tp = next_tp;
		}
	      else
		{
		  /* Backtrack.  */
		  LOG_DBG ((LOG_NEGOTIATION, 30,
			    "message_negotiate_sa: proposal %d failed",
			    GET_ISAKMP_PROP_NO (propp->p)));
		  next_tp = saved_tp;
		  next_propp = saved_propp;
		  next_sap = saved_sap;
		  suite_ok_so_far = 0;

		  /* Remove potentially succeeded choices from the SA.  */
		  while (TAILQ_FIRST (&sa->protos))
		    TAILQ_REMOVE (&sa->protos, TAILQ_FIRST (&sa->protos),
				  link);
		  goto retry_transform;
		}
	    }
	}

      /* Have we walked all the proposals of an SA?  */
      if (!next_tp || sap != next_sap)
	{
	  if (!suite_ok_so_far)
	    {
	      /*
	       * XXX We cannot possibly call this a drop... seeing we just turn
	       * down one of the offers, can we?  I suggest renaming
	       * message_drop to something else.
	       */
	      log_print ("message_negotiate_sa: no compatible proposal found");
	      message_drop (msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
	    }
	  sa = TAILQ_NEXT (sa, next);
	}
    }
  return 0;

 cleanup:
  /*
   * Remove potentially succeeded choices from the SA.
   * XXX Do we leak struct protos and related data here?
   */
  while (TAILQ_FIRST (&sa->protos))
    TAILQ_REMOVE (&sa->protos, TAILQ_FIRST (&sa->protos), link);
  return -1;
}

/*
 * Add SA, proposal and transform payload(s) to MSG out of information
 * found in the exchange MSG is part of..
 */
int
message_add_sa_payload (struct message *msg)
{
  struct exchange *exchange = msg->exchange;
  u_int8_t *sa_buf, *saved_nextp_sa, *saved_nextp_prop;
  size_t sa_len, extra_sa_len;
  int i, nprotos = 0;
  struct proto *proto;
  u_int8_t **transforms = 0, **proposals = 0;
  size_t *transform_lens = 0, *proposal_lens = 0;
  struct sa *sa;
  struct doi *doi = exchange->doi;
  u_int8_t *spi = 0;
  size_t spi_sz;

  /*
   * Generate SA payloads.
   */
  for (sa = TAILQ_FIRST (&exchange->sa_list); sa;
       sa = TAILQ_NEXT (sa, next))
    {
      /* Setup a SA payload.  */
      sa_len = ISAKMP_SA_SIT_OFF + doi->situation_size ();
      extra_sa_len = 0;
      sa_buf = malloc (sa_len);
      if (!sa_buf)
	{
	  log_error ("message_add_sa_payload: malloc (%d) failed", sa_len);
	  goto cleanup;
	}

      SET_ISAKMP_SA_DOI (sa_buf, doi->id);
      doi->setup_situation (sa_buf);

      /* Count transforms.  */
      nprotos = 0;
      for (proto = TAILQ_FIRST (&sa->protos); proto;
	   proto = TAILQ_NEXT (proto, link))
	nprotos++;

      /* Allocate transient transform and proposal payload/size vectors.  */
      transforms = calloc (nprotos, sizeof *transforms);
      if (!transforms)
	{
	  log_error ("message_add_sa_payload: calloc (%d, %d) failed", nprotos,
		     sizeof *transforms);
	  goto cleanup;
	}

      transform_lens = calloc (nprotos, sizeof *transform_lens);
      if (!transform_lens)
	{
	  log_error ("message_add_sa_payload: calloc (%d, %d) failed", nprotos,
		     sizeof *transform_lens);
	  goto cleanup;
	}

      proposals = calloc (nprotos, sizeof *proposals);
      if (!proposals)
	{
	  log_error ("message_add_sa_payload: calloc (%d, %d) failed", nprotos,
		     sizeof *proposals);
	  goto cleanup;
	}

      proposal_lens = calloc (nprotos, sizeof *proposal_lens);
      if (!proposal_lens)
	{
	  log_error ("message_add_sa_payload: calloc (%d, %d) failed", nprotos,
		     sizeof *proposal_lens);
	  goto cleanup;
	}

      /* Pick out the chosen transforms.  */
      for (proto = TAILQ_FIRST (&sa->protos), i = 0; proto;
	   proto = TAILQ_NEXT (proto, link), i++)
	{
	  transform_lens[i] = GET_ISAKMP_GEN_LENGTH (proto->chosen->p);
	  transforms[i] = malloc (transform_lens[i]);
	  if (!transforms[i])
	    {
	      log_error ("message_add_sa_payload: malloc (%d) failed",
			 transform_lens[i]);
	      goto cleanup;
	    }

	  /* Get incoming SPI from application.  */
	  if (doi->get_spi)
	    {
	      spi = doi->get_spi (&spi_sz,
				  GET_ISAKMP_PROP_PROTO (proto->chosen
							 ->context->p),
				  msg);
	      if (spi_sz && !spi)
		goto cleanup;
	      proto->spi[1] = spi;
	      proto->spi_sz[1] = spi_sz;
	    }
	  else
	    spi_sz = 0;

	  proposal_lens[i] = ISAKMP_PROP_SPI_OFF + spi_sz;
	  proposals[i] = malloc (proposal_lens[i]);
	  if (!proposals[i])
	    {
	      log_error ("message_add_sa_payload: malloc (%d) failed",
			 proposal_lens[i]);
	      goto cleanup;
	    }

	  memcpy (transforms[i], proto->chosen->p, transform_lens[i]);
	  memcpy (proposals[i], proto->chosen->context->p,
		  ISAKMP_PROP_SPI_OFF);
	  SET_ISAKMP_PROP_NTRANSFORMS (proposals[i], 1);
	  SET_ISAKMP_PROP_SPI_SZ (proposals[i], spi_sz);
	  if (spi_sz)
	    memcpy (proposals[i] + ISAKMP_PROP_SPI_OFF, spi, spi_sz);
	  extra_sa_len += proposal_lens[i] + transform_lens[i];
	}

      /*
       * Add the payloads.  As this is a SA, we need to recompute the
       * lengths of the payloads containing others.  We also need to
       * reset these payload's "next payload type" field.
       */
      if (message_add_payload (msg, ISAKMP_PAYLOAD_SA, sa_buf, sa_len, 1))
	goto cleanup;
      SET_ISAKMP_GEN_LENGTH (sa_buf, sa_len + extra_sa_len);
      sa_buf = 0;

      saved_nextp_sa = msg->nextp;
      for (proto = TAILQ_FIRST (&sa->protos), i = 0; proto;
	   proto = TAILQ_NEXT (proto, link), i++)
	{
	  if (message_add_payload (msg, ISAKMP_PAYLOAD_PROPOSAL, proposals[i],
				   proposal_lens[i], i > 1))
	    goto cleanup;
	  SET_ISAKMP_GEN_LENGTH (proposals[i],
				 proposal_lens[i] + transform_lens[i]);
	  proposals[i] = 0;

	  saved_nextp_prop = msg->nextp;
	  if (message_add_payload (msg, ISAKMP_PAYLOAD_TRANSFORM,
				   transforms[i], transform_lens[i], 0))
	    goto cleanup;
	  msg->nextp = saved_nextp_prop;
	  transforms[i] = 0;
	}
      msg->nextp = saved_nextp_sa;

      /* Free the temporary allocations made above.  */
      free (transforms);
      free (transform_lens);
      free (proposals);
      free (proposal_lens);
    }
  return 0;

 cleanup:
  if (sa_buf)
    free (sa_buf);
  for (i = 0; i < nprotos; i++)
    {
      if (transforms[i])
	free (transforms[i]);
      if (proposals[i])
	free (proposals[i]);
    }
  if (transforms)
    free (transforms);
  if (transform_lens)
    free (transform_lens);
  if (proposals)
    free (proposals);
  if (proposal_lens)
    free (proposal_lens);
  return -1;
}

/*
 * Return a copy of MSG's constants starting from OFFSET and stash the size
 * in SZP.  It is the callers responsibility to free this up.
 */
u_int8_t *
message_copy (struct message *msg, size_t offset, size_t *szp)
{
  int i, skip = 0;
  size_t sz = 0;
  ssize_t start = -1;
  u_int8_t *buf, *p;

  /* Calculate size of message and where we want to start to copy.  */
  for (i = 1; i < msg->iovlen; i++)
    {
      sz += msg->iov[i].iov_len;
      if (sz <= offset)
	skip = i;
      else if (start < 0)
	start = offset - (sz - msg->iov[i].iov_len);
    }

  /* Allocate and copy.  */
  *szp = sz - offset;
  buf = malloc (*szp);
  if (!buf)
    return 0;
  p = buf;
  for (i = skip + 1; i < msg->iovlen; i++)
    {
      memcpy (p, msg->iov[i].iov_base + start, msg->iov[i].iov_len - start);
      p += msg->iov[i].iov_len - start;
      start = 0;
    }
  return buf;
}

/* Register a post-send function POST_SEND with message MSG.  */
int
message_register_post_send (struct message *msg,
			    void (*post_send) (struct message *))
{
  struct post_send *node;

  node = malloc (sizeof *node);
  if (!node)
    return -1;
  node->func = post_send;
  TAILQ_INSERT_TAIL (&msg->post_send, node, link);
  return 0;
}

/* Run the post-send functions of message MSG.  */
void
message_post_send (struct message *msg)
{
  struct post_send *node;

  while ((node = TAILQ_FIRST (&msg->post_send)) != 0)
    {
      TAILQ_REMOVE (&msg->post_send, node, link);
      node->func (msg);
      free (node);
    }
}