OpenBSD-4.6/sbin/isakmpd/ike_quick_mode.c

Compare this file to the similar file:
Show the results in this format:

/* $OpenBSD: ike_quick_mode.c,v 1.102 2008/11/11 15:11:28 hshoexer Exp $	 */
/* $EOM: ike_quick_mode.c,v 1.139 2001/01/26 10:43:17 niklas Exp $	 */

/*
 * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis.  All rights reserved.
 * Copyright (c) 2000, 2001, 2004 Håkan Olsson.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

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

#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <regex.h>
#include <keynote.h>

#include "attribute.h"
#include "conf.h"
#include "connection.h"
#include "dh.h"
#include "doi.h"
#include "exchange.h"
#include "hash.h"
#include "ike_quick_mode.h"
#include "ipsec.h"
#include "log.h"
#include "math_group.h"
#include "message.h"
#include "policy.h"
#include "prf.h"
#include "sa.h"
#include "transport.h"
#include "util.h"
#include "key.h"
#include "x509.h"

static void     gen_g_xy(struct message *);
static int      initiator_send_HASH_SA_NONCE(struct message *);
static int      initiator_recv_HASH_SA_NONCE(struct message *);
static int      initiator_send_HASH(struct message *);
static void     post_quick_mode(struct message *);
static int      responder_recv_HASH_SA_NONCE(struct message *);
static int      responder_send_HASH_SA_NONCE(struct message *);
static int      responder_recv_HASH(struct message *);

static int      check_policy(struct exchange *, struct sa *, struct sa *);

int	(*ike_quick_mode_initiator[])(struct message *) = {
	initiator_send_HASH_SA_NONCE,
	initiator_recv_HASH_SA_NONCE,
	initiator_send_HASH
};

int	(*ike_quick_mode_responder[])(struct message *) = {
	responder_recv_HASH_SA_NONCE,
	responder_send_HASH_SA_NONCE,
	responder_recv_HASH
};

/* How many return values will policy handle -- true/false for now */
#define RETVALUES_NUM 2

/*
 * Given an exchange and our policy, check whether the SA and IDs are
 * acceptable.
 */
static int
check_policy(struct exchange *exchange, struct sa *sa, struct sa *isakmp_sa)
{
	char           *return_values[RETVALUES_NUM];
	char          **principal = 0;
	int             i, len, result = 0, nprinc = 0;
	int            *x509_ids = 0, *keynote_ids = 0;
	unsigned char   hashbuf[20];	/* Set to the largest digest result */
	struct keynote_deckey dc;
	X509_NAME      *subject;

	/* Do we want to use keynote policies? */
	if (ignore_policy ||
	    strncmp("yes", conf_get_str("General", "Use-Keynote"), 3))
		return 1;

	/* Initialize if necessary -- e.g., if pre-shared key auth was used */
	if (isakmp_sa->policy_id < 0) {
		if ((isakmp_sa->policy_id = kn_init()) == -1) {
			log_print("check_policy: "
			    "failed to initialize policy session");
			return 0;
		}
	}
	/* Add the callback that will handle attributes.  */
	if (kn_add_action(isakmp_sa->policy_id, ".*", (char *)policy_callback,
	    ENVIRONMENT_FLAG_FUNC | ENVIRONMENT_FLAG_REGEX) == -1) {
		log_print("check_policy: "
		    "kn_add_action (%d, \".*\", %p, FUNC | REGEX) failed",
		    isakmp_sa->policy_id, policy_callback);
		kn_close(isakmp_sa->policy_id);
		isakmp_sa->policy_id = -1;
		return 0;
	}
	if (policy_asserts_num) {
		keynote_ids = calloc(policy_asserts_num, sizeof *keynote_ids);
		if (!keynote_ids) {
			log_error("check_policy: calloc (%d, %lu) failed",
			    policy_asserts_num,
			    (unsigned long)sizeof *keynote_ids);
			kn_close(isakmp_sa->policy_id);
			isakmp_sa->policy_id = -1;
			return 0;
		}
	}
	/* Add the policy assertions */
	for (i = 0; i < policy_asserts_num; i++)
		keynote_ids[i] = kn_add_assertion(isakmp_sa->policy_id,
		    policy_asserts[i],
		    strlen(policy_asserts[i]), ASSERT_FLAG_LOCAL);

	/* Initialize -- we'll let the callback do all the work.  */
	policy_exchange = exchange;
	policy_sa = sa;
	policy_isakmp_sa = isakmp_sa;

	/* Set the return values; true/false for now at least.  */
	return_values[0] = "false";	/* Order of values in array is
					 * important.  */
	return_values[1] = "true";

	/* Create a principal (authorizer) for the SA/ID request.  */
	switch (isakmp_sa->recv_certtype) {
	case ISAKMP_CERTENC_NONE:
		/*
		 * For shared keys, just duplicate the passphrase with the
		 * appropriate prefix tag.
		 */
		nprinc = 3;
		principal = calloc(nprinc, sizeof *principal);
		if (!principal) {
			log_error("check_policy: calloc (%d, %lu) failed",
			    nprinc, (unsigned long)sizeof *principal);
			goto policydone;
		}
		len = strlen(isakmp_sa->recv_key) + sizeof "passphrase:";
		principal[0] = calloc(len, sizeof(char));
		if (!principal[0]) {
			log_error("check_policy: calloc (%d, %lu) failed", len,
			    (unsigned long)sizeof(char));
			goto policydone;
		}
		/*
		 * XXX Consider changing the magic hash lengths with
		 * constants.
		 */
		strlcpy(principal[0], "passphrase:", len);
		memcpy(principal[0] + sizeof "passphrase:" - 1,
		    isakmp_sa->recv_key, strlen(isakmp_sa->recv_key));

		len = sizeof "passphrase-md5-hex:" + 2 * 16;
		principal[1] = calloc(len, sizeof(char));
		if (!principal[1]) {
			log_error("check_policy: calloc (%d, %lu) failed", len,
			    (unsigned long)sizeof(char));
			goto policydone;
		}
		strlcpy(principal[1], "passphrase-md5-hex:", len);
		MD5(isakmp_sa->recv_key, strlen(isakmp_sa->recv_key), hashbuf);
		for (i = 0; i < 16; i++)
			snprintf(principal[1] + 2 * i +
			    sizeof "passphrase-md5-hex:" - 1, 3, "%02x",
			    hashbuf[i]);

		len = sizeof "passphrase-sha1-hex:" + 2 * 20;
		principal[2] = calloc(len, sizeof(char));
		if (!principal[2]) {
			log_error("check_policy: calloc (%d, %lu) failed", len,
			    (unsigned long)sizeof(char));
			goto policydone;
		}
		strlcpy(principal[2], "passphrase-sha1-hex:", len);
		SHA1(isakmp_sa->recv_key, strlen(isakmp_sa->recv_key),
		    hashbuf);
		for (i = 0; i < 20; i++)
			snprintf(principal[2] + 2 * i +
			    sizeof "passphrase-sha1-hex:" - 1, 3, "%02x",
			    hashbuf[i]);
		break;

	case ISAKMP_CERTENC_KEYNOTE:
		nprinc = 1;

		principal = calloc(nprinc, sizeof *principal);
		if (!principal) {
			log_error("check_policy: calloc (%d, %lu) failed",
			    nprinc, (unsigned long)sizeof *principal);
			goto policydone;
		}
		/* Dup the keys */
		principal[0] = strdup(isakmp_sa->keynote_key);
		if (!principal[0]) {
			log_error("check_policy: calloc (%lu, %lu) failed",
			    (unsigned long)strlen(isakmp_sa->keynote_key),
			    (unsigned long)sizeof(char));
			goto policydone;
		}
		break;

	case ISAKMP_CERTENC_X509_SIG:
		principal = calloc(2, sizeof *principal);
		if (!principal) {
			log_error("check_policy: calloc (2, %lu) failed",
			    (unsigned long)sizeof *principal);
			goto policydone;
		}
		if (isakmp_sa->recv_keytype == ISAKMP_KEY_RSA)
			dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
		else {
			log_error("check_policy: "
			    "unknown/unsupported public key algorithm %d",
			    isakmp_sa->recv_keytype);
			goto policydone;
		}

		dc.dec_key = isakmp_sa->recv_key;
		principal[0] = kn_encode_key(&dc, INTERNAL_ENC_PKCS1,
		    ENCODING_HEX, KEYNOTE_PUBLIC_KEY);
		if (keynote_errno == ERROR_MEMORY) {
			log_print("check_policy: "
			    "failed to get memory for public key");
			goto policydone;
		}
		if (!principal[0]) {
			log_print("check_policy: "
			    "failed to allocate memory for principal");
			goto policydone;
		}
		if (asprintf(&principal[1], "rsa-hex:%s", principal[0]) == -1) {
			log_error("check_policy: asprintf() failed");
			goto policydone;
		}
		free(principal[0]);
		principal[0] = principal[1];
		principal[1] = 0;

		/* Generate a "DN:" principal.  */
		subject = X509_get_subject_name(isakmp_sa->recv_cert);
		if (subject) {
			principal[1] = calloc(259, sizeof(char));
			if (!principal[1]) {
				log_error("check_policy: "
				    "calloc (259, %lu) failed",
				    (unsigned long)sizeof(char));
				goto policydone;
			}
			strlcpy(principal[1], "DN:", 259);
			X509_NAME_oneline(subject, principal[1] + 3, 256);
			nprinc = 2;
		} else {
			nprinc = 1;
		}
		break;

		/* XXX Eventually handle these.  */
	case ISAKMP_CERTENC_PKCS:
	case ISAKMP_CERTENC_PGP:
	case ISAKMP_CERTENC_DNS:
	case ISAKMP_CERTENC_X509_KE:
	case ISAKMP_CERTENC_KERBEROS:
	case ISAKMP_CERTENC_CRL:
	case ISAKMP_CERTENC_ARL:
	case ISAKMP_CERTENC_SPKI:
	case ISAKMP_CERTENC_X509_ATTR:
	default:
		log_print("check_policy: "
		    "unknown/unsupported certificate/authentication method %d",
		    isakmp_sa->recv_certtype);
		goto policydone;
	}

	/*
	 * Add the authorizer (who is requesting the SA/ID);
	 * this may be a public or a secret key, depending on
	 * what mode of authentication we used in Phase 1.
         */
	for (i = 0; i < nprinc; i++) {
		LOG_DBG((LOG_POLICY, 40, "check_policy: "
		    "adding authorizer [%s]", principal[i]));

		if (kn_add_authorizer(isakmp_sa->policy_id, principal[i])
		    == -1) {
			int	j;

			for (j = 0; j < i; j++)
				kn_remove_authorizer(isakmp_sa->policy_id,
				    principal[j]);
			log_print("check_policy: kn_add_authorizer failed");
			goto policydone;
		}
	}

	/* Ask policy */
	result = kn_do_query(isakmp_sa->policy_id, return_values,
	    RETVALUES_NUM);
	LOG_DBG((LOG_POLICY, 40, "check_policy: kn_do_query returned %d",
	    result));

	/* Cleanup environment */
	kn_cleanup_action_environment(isakmp_sa->policy_id);

	/* Remove authorizers from the session */
	for (i = 0; i < nprinc; i++) {
		kn_remove_authorizer(isakmp_sa->policy_id, principal[i]);
		free(principal[i]);
	}

	free(principal);
	principal = 0;
	nprinc = 0;

	/* Check what policy said.  */
	if (result < 0) {
		LOG_DBG((LOG_POLICY, 40, "check_policy: proposal refused"));
		result = 0;
		goto policydone;
	}
policydone:
	for (i = 0; i < nprinc; i++)
		if (principal && principal[i])
			free(principal[i]);

	free(principal);

	/* Remove the policies */
	for (i = 0; i < policy_asserts_num; i++) {
		if (keynote_ids[i] != -1)
			kn_remove_assertion(isakmp_sa->policy_id,
			    keynote_ids[i]);
	}

	free(keynote_ids);

	free(x509_ids);

	/*
	 * XXX Currently, check_policy() is only called from
	 * message_negotiate_sa(), and so this log message reflects this.
	 * Change to something better?
         */
	if (result == 0)
		log_print("check_policy: negotiated SA failed policy check");

	/*
	 * Given that we have only 2 return values from policy (true/false)
	 * we can just return the query result directly (no pre-processing
	 * needed).
         */
	return result;
}

/*
 * Offer several sets of transforms to the responder.
 * XXX Split this huge function up and look for common code with main mode.
 */
static int
initiator_send_HASH_SA_NONCE(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct doi     *doi = exchange->doi;
	struct ipsec_exch *ie = exchange->data;
	u_int8_t     ***transform = 0, ***new_transform;
	u_int8_t      **proposal = 0, **new_proposal;
	u_int8_t       *sa_buf = 0, *attr, *saved_nextp_sa, *saved_nextp_prop,
	               *id, *spi;
	size_t          spi_sz, sz;
	size_t          proposal_len = 0, proposals_len = 0, sa_len;
	size_t        **transform_len = 0, **new_transform_len;
	size_t         *transforms_len = 0, *new_transforms_len;
	u_int32_t      *transform_cnt = 0, *new_transform_cnt;
	u_int32_t       suite_no, prop_no, prot_no, xf_no, prop_cnt = 0;
	u_int32_t       i;
	int             value, update_nextp, protocol_num, proto_id;
	struct proto   *proto;
	struct conf_list *suite_conf, *prot_conf = 0, *xf_conf = 0, *life_conf;
	struct conf_list_node *suite, *prot, *xf, *life;
	struct constant_map *id_map;
	char           *protocol_id, *transform_id;
	char           *local_id, *remote_id;
	int             group_desc = -1, new_group_desc;
	struct ipsec_sa *isa = msg->isakmp_sa->data;
	struct hash    *hash = hash_get(isa->hash);
	struct sockaddr *src;
	struct proto_attr *pa;

	if (!ipsec_add_hash_payload(msg, hash->hashsize))
		return -1;

	/* Get the list of protocol suites.  */
	suite_conf = conf_get_list(exchange->policy, "Suites");
	if (!suite_conf)
		return -1;

	for (suite = TAILQ_FIRST(&suite_conf->fields), suite_no = prop_no = 0;
	    suite_no < suite_conf->cnt;
	    suite_no++, suite = TAILQ_NEXT(suite, link)) {
		/* Now get each protocol in this specific protocol suite.  */
		prot_conf = conf_get_list(suite->field, "Protocols");
		if (!prot_conf)
			goto bail_out;

		for (prot = TAILQ_FIRST(&prot_conf->fields), prot_no = 0;
		    prot_no < prot_conf->cnt;
		    prot_no++, prot = TAILQ_NEXT(prot, link)) {
			/* Make sure we have a proposal/transform vectors.  */
			if (prop_no >= prop_cnt) {
				/*
				 * This resize algorithm is completely
				 * arbitrary.
				 */
				prop_cnt = 2 * prop_cnt + 10;
				new_proposal = realloc(proposal,
				    prop_cnt * sizeof *proposal);
				if (!new_proposal) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "realloc (%p, %lu) failed",
					    proposal,
					    prop_cnt * (unsigned long)sizeof *proposal);
					goto bail_out;
				}
				proposal = new_proposal;

				new_transforms_len = realloc(transforms_len,
				    prop_cnt * sizeof *transforms_len);
				if (!new_transforms_len) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "realloc (%p, %lu) failed",
					    transforms_len,
					    prop_cnt * (unsigned long)sizeof *transforms_len);
					goto bail_out;
				}
				transforms_len = new_transforms_len;

				new_transform = realloc(transform,
				    prop_cnt * sizeof *transform);
				if (!new_transform) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "realloc (%p, %lu) failed",
					    transform,
					    prop_cnt * (unsigned long)sizeof *transform);
					goto bail_out;
				}
				transform = new_transform;

				new_transform_cnt = realloc(transform_cnt,
				    prop_cnt * sizeof *transform_cnt);
				if (!new_transform_cnt) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "realloc (%p, %lu) failed",
					    transform_cnt,
					    prop_cnt * (unsigned long)sizeof *transform_cnt);
					goto bail_out;
				}
				transform_cnt = new_transform_cnt;

				new_transform_len = realloc(transform_len,
				    prop_cnt * sizeof *transform_len);
				if (!new_transform_len) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "realloc (%p, %lu) failed",
					    transform_len,
					    prop_cnt * (unsigned long)sizeof *transform_len);
					goto bail_out;
				}
				transform_len = new_transform_len;
			}
			protocol_id = conf_get_str(prot->field, "PROTOCOL_ID");
			if (!protocol_id)
				goto bail_out;

			proto_id = constant_value(ipsec_proto_cst,
			    protocol_id);
			switch (proto_id) {
			case IPSEC_PROTO_IPSEC_AH:
				id_map = ipsec_ah_cst;
				break;

			case IPSEC_PROTO_IPSEC_ESP:
				id_map = ipsec_esp_cst;
				break;

			case IPSEC_PROTO_IPCOMP:
				id_map = ipsec_ipcomp_cst;
				break;

			default:
			    {
				log_print("initiator_send_HASH_SA_NONCE: "
				    "invalid PROTCOL_ID: %s", protocol_id);
				goto bail_out;
			    }
			}

			/* Now get each transform we offer for this protocol.*/
			xf_conf = conf_get_list(prot->field, "Transforms");
			if (!xf_conf)
				goto bail_out;
			transform_cnt[prop_no] = xf_conf->cnt;

			transform[prop_no] = calloc(transform_cnt[prop_no],
			    sizeof **transform);
			if (!transform[prop_no]) {
				log_error("initiator_send_HASH_SA_NONCE: "
				    "calloc (%d, %lu) failed",
				    transform_cnt[prop_no],
				    (unsigned long)sizeof **transform);
				goto bail_out;
			}
			transform_len[prop_no] = calloc(transform_cnt[prop_no],
			    sizeof **transform_len);
			if (!transform_len[prop_no]) {
				log_error("initiator_send_HASH_SA_NONCE: "
				    "calloc (%d, %lu) failed",
				    transform_cnt[prop_no],
				    (unsigned long)sizeof **transform_len);
				goto bail_out;
			}
			transforms_len[prop_no] = 0;
			for (xf = TAILQ_FIRST(&xf_conf->fields), xf_no = 0;
			    xf_no < transform_cnt[prop_no];
			    xf_no++, xf = TAILQ_NEXT(xf, link)) {

				/* XXX The sizing needs to be dynamic.  */
				transform[prop_no][xf_no] =
				    calloc(ISAKMP_TRANSFORM_SA_ATTRS_OFF +
				    9 * ISAKMP_ATTR_VALUE_OFF, 1);
				if (!transform[prop_no][xf_no]) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "calloc (%d, 1) failed",
					    ISAKMP_TRANSFORM_SA_ATTRS_OFF +
					    9 * ISAKMP_ATTR_VALUE_OFF);
					goto bail_out;
				}
				SET_ISAKMP_TRANSFORM_NO(transform[prop_no][xf_no],
				    xf_no + 1);

				transform_id = conf_get_str(xf->field,
				    "TRANSFORM_ID");
				if (!transform_id)
					goto bail_out;
				SET_ISAKMP_TRANSFORM_ID(transform[prop_no][xf_no],
				    constant_value(id_map, transform_id));
				SET_ISAKMP_TRANSFORM_RESERVED(transform[prop_no][xf_no], 0);

				attr = transform[prop_no][xf_no] +
				    ISAKMP_TRANSFORM_SA_ATTRS_OFF;

				/*
				 * Life durations are special, we should be
				 * able to specify several, one per type.
				 */
				life_conf = conf_get_list(xf->field, "Life");
				if (life_conf) {
					for (life = TAILQ_FIRST(&life_conf->fields);
					    life; life = TAILQ_NEXT(life, link)) {
						attribute_set_constant(
						    life->field, "LIFE_TYPE",
						    ipsec_duration_cst,
						    IPSEC_ATTR_SA_LIFE_TYPE,
						    &attr);

						/*
						 * XXX Deals with 16 and 32
						 * bit lifetimes only
						 */
						value =
						    conf_get_num(life->field,
							"LIFE_DURATION", 0);
						if (value) {
							if (value <= 0xffff)
								attr =
								    attribute_set_basic(
									attr,
									IPSEC_ATTR_SA_LIFE_DURATION,
									value);
							else {
								value = htonl(value);
								attr =
								    attribute_set_var(
									attr,
									IPSEC_ATTR_SA_LIFE_DURATION,
									(u_int8_t *)&value,
									sizeof value);
							}
						}
					}
					conf_free_list(life_conf);
				}
				attribute_set_constant(xf->field,
				    "ENCAPSULATION_MODE", ipsec_encap_cst,
				    IPSEC_ATTR_ENCAPSULATION_MODE, &attr);

				if (proto_id != IPSEC_PROTO_IPCOMP) {
					attribute_set_constant(xf->field,
					    "AUTHENTICATION_ALGORITHM",
					    ipsec_auth_cst,
					    IPSEC_ATTR_AUTHENTICATION_ALGORITHM,
					    &attr);

					attribute_set_constant(xf->field,
					    "GROUP_DESCRIPTION",
					    ike_group_desc_cst,
					    IPSEC_ATTR_GROUP_DESCRIPTION, &attr);

					value = conf_get_num(xf->field,
					    "KEY_LENGTH", 0);
					if (value)
						attr = attribute_set_basic(
						    attr,
						    IPSEC_ATTR_KEY_LENGTH,
						    value);

					value = conf_get_num(xf->field,
					    "KEY_ROUNDS", 0);
					if (value)
						attr = attribute_set_basic(
						    attr,
						    IPSEC_ATTR_KEY_ROUNDS,
						    value);
				} else {
					value = conf_get_num(xf->field,
					    "COMPRESS_DICTIONARY_SIZE", 0);
					if (value)
						attr = attribute_set_basic(
						    attr,
						    IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE,
						    value);

					value = conf_get_num(xf->field,
					   "COMPRESS_PRIVATE_ALGORITHM", 0);
					if (value)
						attr = attribute_set_basic(
						    attr,
						    IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM,
						    value);
				}

				value = conf_get_num(xf->field, "ECN_TUNNEL",
				    0);
				if (value)
					attr = attribute_set_basic(attr,
					    IPSEC_ATTR_ECN_TUNNEL, value);

				/* Record the real transform size.  */
				transforms_len[prop_no] +=
				    (transform_len[prop_no][xf_no]
					= attr - transform[prop_no][xf_no]);

				if (proto_id != IPSEC_PROTO_IPCOMP) {
					/*
					 * Make sure that if a group
					 * description is specified, it is
					 * specified for all transforms
					 * equally.
					 */
					attr =
					    (u_int8_t *)conf_get_str(xf->field,
						"GROUP_DESCRIPTION");
					new_group_desc
					    = attr ? constant_value(ike_group_desc_cst,
						(char *)attr) : 0;
					if (group_desc == -1)
						group_desc = new_group_desc;
					else if (group_desc != new_group_desc) {
						log_print("initiator_send_HASH_SA_NONCE: "
						    "differing group descriptions in a proposal");
						goto bail_out;
					}
				}
			}
			conf_free_list(xf_conf);
			xf_conf = 0;

			/*
			 * Get SPI from application.
			 * XXX Should we care about unknown constants?
			 */
			protocol_num = constant_value(ipsec_proto_cst,
			    protocol_id);
			spi = doi->get_spi(&spi_sz, protocol_num, msg);
			if (spi_sz && !spi) {
				log_print("initiator_send_HASH_SA_NONCE: "
				    "doi->get_spi failed");
				goto bail_out;
			}
			proposal_len = ISAKMP_PROP_SPI_OFF + spi_sz;
			proposals_len +=
			    proposal_len + transforms_len[prop_no];
			proposal[prop_no] = malloc(proposal_len);
			if (!proposal[prop_no]) {
				log_error("initiator_send_HASH_SA_NONCE: "
				    "malloc (%lu) failed",
				    (unsigned long)proposal_len);
				goto bail_out;
			}
			SET_ISAKMP_PROP_NO(proposal[prop_no], suite_no + 1);
			SET_ISAKMP_PROP_PROTO(proposal[prop_no], protocol_num);

			/* XXX I would like to see this factored out.  */
			proto = calloc(1, sizeof *proto);
			if (!proto) {
				log_error("initiator_send_HASH_SA_NONCE: "
				    "calloc (1, %lu) failed",
				    (unsigned long)sizeof *proto);
				goto bail_out;
			}
			if (doi->proto_size) {
				proto->data = calloc(1, doi->proto_size);
				if (!proto->data) {
					log_error(
					    "initiator_send_HASH_SA_NONCE: "
					    "calloc (1, %lu) failed",
					    (unsigned long)doi->proto_size);
					goto bail_out;
				}
			}
			proto->no = suite_no + 1;
			proto->proto = protocol_num;
			proto->sa = TAILQ_FIRST(&exchange->sa_list);
			proto->xf_cnt = transform_cnt[prop_no];
			TAILQ_INIT(&proto->xfs);
			for (xf_no = 0; xf_no < proto->xf_cnt; xf_no++) {
				pa = (struct proto_attr *)calloc(1,
				    sizeof *pa);
				if (!pa)
					goto bail_out;
				pa->len = transform_len[prop_no][xf_no];
				pa->attrs = (u_int8_t *)malloc(pa->len);
				if (!pa->attrs) {
					free(pa);
					goto bail_out;
				}
				memcpy(pa->attrs, transform[prop_no][xf_no],
				    pa->len);
				TAILQ_INSERT_TAIL(&proto->xfs, pa, next);
			}
			TAILQ_INSERT_TAIL(&TAILQ_FIRST(&exchange->sa_list)->protos,
			    proto, link);

			/* Setup the incoming SPI.  */
			SET_ISAKMP_PROP_SPI_SZ(proposal[prop_no], spi_sz);
			memcpy(proposal[prop_no] + ISAKMP_PROP_SPI_OFF, spi,
			    spi_sz);
			proto->spi_sz[1] = spi_sz;
			proto->spi[1] = spi;

			/*
			 * Let the DOI get at proto for initializing its own
			 * data.
			 */
			if (doi->proto_init)
				doi->proto_init(proto, prot->field);

			SET_ISAKMP_PROP_NTRANSFORMS(proposal[prop_no],
						    transform_cnt[prop_no]);
			prop_no++;
		}
		conf_free_list(prot_conf);
		prot_conf = 0;
	}

	sa_len = ISAKMP_SA_SIT_OFF + IPSEC_SIT_SIT_LEN;
	sa_buf = malloc(sa_len);
	if (!sa_buf) {
		log_error("initiator_send_HASH_SA_NONCE: malloc (%lu) failed",
			  (unsigned long)sa_len);
		goto bail_out;
	}
	SET_ISAKMP_SA_DOI(sa_buf, IPSEC_DOI_IPSEC);
	SET_IPSEC_SIT_SIT(sa_buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY);

	/*
	 * 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 bail_out;
	SET_ISAKMP_GEN_LENGTH(sa_buf, sa_len + proposals_len);
	sa_buf = 0;

	update_nextp = 0;
	saved_nextp_sa = msg->nextp;
	for (i = 0; i < prop_no; i++) {
		if (message_add_payload(msg, ISAKMP_PAYLOAD_PROPOSAL,
		    proposal[i], proposal_len, update_nextp))
			goto bail_out;
		SET_ISAKMP_GEN_LENGTH(proposal[i],
		    proposal_len + transforms_len[i]);
		proposal[i] = 0;

		update_nextp = 0;
		saved_nextp_prop = msg->nextp;
		for (xf_no = 0; xf_no < transform_cnt[i]; xf_no++) {
			if (message_add_payload(msg, ISAKMP_PAYLOAD_TRANSFORM,
			    transform[i][xf_no],
			    transform_len[i][xf_no], update_nextp))
				goto bail_out;
			update_nextp = 1;
			transform[i][xf_no] = 0;
		}
		msg->nextp = saved_nextp_prop;
		update_nextp = 1;
	}
	msg->nextp = saved_nextp_sa;

	/*
	 * Save SA payload body in ie->sa_i_b, length ie->sa_i_b_len.
         */
	ie->sa_i_b = message_copy(msg, ISAKMP_GEN_SZ, &ie->sa_i_b_len);
	if (!ie->sa_i_b)
		goto bail_out;

	/*
	 * Generate a nonce, and add it to the message.
	 * XXX I want a better way to specify the nonce's size.
         */
	if (exchange_gen_nonce(msg, 16))
		return -1;

	/* Generate optional KEY_EXCH payload.  */
	if (group_desc > 0) {
		ie->group = group_get(group_desc);
		ie->g_x_len = dh_getlen(ie->group);

		if (ipsec_gen_g_x(msg)) {
			group_free(ie->group);
			ie->group = 0;
			return -1;
		}
	}
	/* Generate optional client ID payloads.  XXX Share with responder.  */
	local_id = conf_get_str(exchange->name, "Local-ID");
	remote_id = conf_get_str(exchange->name, "Remote-ID");
	if (local_id && remote_id) {
		id = ipsec_build_id(local_id, &sz);
		if (!id)
			return -1;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_send_HASH_SA_NONCE: IDic", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
		id = ipsec_build_id(remote_id, &sz);
		if (!id)
			return -1;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_send_HASH_SA_NONCE: IDrc", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
	}
	/* XXX I do not judge these as errors, are they?  */
	else if (local_id)
		log_print("initiator_send_HASH_SA_NONCE: "
			  "Local-ID given without Remote-ID for \"%s\"",
			  exchange->name);
	else if (remote_id)
		/*
		 * This code supports the "road warrior" case, where the
		 * initiator doesn't have a fixed IP address, but wants to
		 * specify a particular remote network to talk to. -- Adrian
		 * Close <adrian@esec.com.au>
		 */
	{
		log_print("initiator_send_HASH_SA_NONCE: "
			  "Remote-ID given without Local-ID for \"%s\"",
			  exchange->name);

		/*
		 * If we're here, then we are the initiator, so use initiator
		 * address for local ID
		 */
		msg->transport->vtbl->get_src(msg->transport, &src);
		sz = ISAKMP_ID_SZ + sockaddr_addrlen(src);

		id = calloc(sz, sizeof(char));
		if (!id) {
			log_error("initiator_send_HASH_SA_NONCE: "
			    "calloc (%lu, %lu) failed", (unsigned long)sz,
			    (unsigned long)sizeof(char));
			return -1;
		}
		switch (src->sa_family) {
		case AF_INET6:
			SET_ISAKMP_ID_TYPE(id, IPSEC_ID_IPV6_ADDR);
			break;
		case AF_INET:
			SET_ISAKMP_ID_TYPE(id, IPSEC_ID_IPV4_ADDR);
			break;
		default:
			log_error("initiator_send_HASH_SA_NONCE: "
			    "unknown sa_family %d", src->sa_family);
			free(id);
			return -1;
		}
		memcpy(id + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src),
		    sockaddr_addrlen(src));

		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_send_HASH_SA_NONCE: IDic", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
		/* Send supplied remote_id */
		id = ipsec_build_id(remote_id, &sz);
		if (!id)
			return -1;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_send_HASH_SA_NONCE: IDrc", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
	}
	if (ipsec_fill_in_hash(msg))
		goto bail_out;

	conf_free_list(suite_conf);
	for (i = 0; i < prop_no; i++) {
		free(transform[i]);
		free(transform_len[i]);
	}
	free(proposal);
	free(transform);
	free(transforms_len);
	free(transform_len);
	free(transform_cnt);
	return 0;

bail_out:
	free(sa_buf);
	if (proposal) {
		for (i = 0; i < prop_no; i++) {
			free(proposal[i]);
			if (transform[i]) {
				for (xf_no = 0; xf_no < transform_cnt[i];
				    xf_no++)
					free(transform[i][xf_no]);
				free(transform[i]);
			}
			free(transform_len[i]);
		}
		free(proposal);
		free(transforms_len);
		free(transform);
		free(transform_len);
		free(transform_cnt);
	}
	if (xf_conf)
		conf_free_list(xf_conf);
	if (prot_conf)
		conf_free_list(prot_conf);
	conf_free_list(suite_conf);
	return -1;
}

/* Figure out what transform the responder chose.  */
static int
initiator_recv_HASH_SA_NONCE(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	struct sa      *sa;
	struct proto   *proto, *next_proto;
	struct payload *sa_p = payload_first(msg, ISAKMP_PAYLOAD_SA);
	struct payload *xf, *idp;
	struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
	struct payload *kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH);
	struct prf     *prf;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct hash    *hash = hash_get(isa->hash);
	u_int8_t       *rest;
	size_t          rest_len;
	struct sockaddr *src, *dst;

	/* Allocate the prf and start calculating our HASH(1).  XXX Share?  */
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_recv_HASH_SA_NONCE: "
	    "SKEYID_a", (u_int8_t *)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_NEGOTIATION, 90,
	    "initiator_recv_HASH_SA_NONCE: message_id",
	    exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
	prf->Update(prf->prfctx, exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_recv_HASH_SA_NONCE: "
	    "NONCE_I_b", exchange->nonce_i, exchange->nonce_i_len));
	prf->Update(prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
	rest = hashp->p + GET_ISAKMP_GEN_LENGTH(hashp->p);
	rest_len = (GET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base)
	    - (rest - (u_int8_t *)msg->iov[0].iov_base));
	LOG_DBG_BUF((LOG_NEGOTIATION, 90,
	    "initiator_recv_HASH_SA_NONCE: payloads after HASH(2)", rest,
	    rest_len));
	prf->Update(prf->prfctx, rest, rest_len);
	prf->Final(hash->digest, prf->prfctx);
	prf_free(prf);
	LOG_DBG_BUF((LOG_NEGOTIATION, 80,
	    "initiator_recv_HASH_SA_NONCE: computed HASH(2)", hash->digest,
	    hash->hashsize));
	if (memcmp(hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest,
	    hash->hashsize) != 0) {
		message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1,
		    0);
		return -1;
	}
	/* Mark the HASH as handled.  */
	hashp->flags |= PL_MARK;

	/* Mark message as authenticated. */
	msg->flags |= MSG_AUTHENTICATED;

	/*
	 * As we are getting an answer on our transform offer, only one
	 * transform should be given.
         *
	 * XXX Currently we only support negotiating one SA per quick mode run.
         */
	if (TAILQ_NEXT(sa_p, link)) {
		log_print("initiator_recv_HASH_SA_NONCE: "
		    "multiple SA payloads in quick mode not supported yet");
		return -1;
	}
	sa = TAILQ_FIRST(&exchange->sa_list);

	/* This is here for the policy check */
	if (kep)
		ie->pfs = 1;

	/* Handle optional client ID payloads.  */
	idp = payload_first(msg, ISAKMP_PAYLOAD_ID);
	if (idp) {
		/* If IDci is there, IDcr must be too.  */
		if (!TAILQ_NEXT(idp, link)) {
			/* XXX Is this a good notify type?  */
			message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0,
			    1, 0);
			return -1;
		}
		/* XXX We should really compare, not override.  */
		ie->id_ci_sz = GET_ISAKMP_GEN_LENGTH(idp->p);
		ie->id_ci = malloc(ie->id_ci_sz);
		if (!ie->id_ci) {
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "malloc (%lu) failed",
			    (unsigned long)ie->id_ci_sz);
			return -1;
		}
		memcpy(ie->id_ci, idp->p, ie->id_ci_sz);
		idp->flags |= PL_MARK;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_recv_HASH_SA_NONCE: IDci",
		    ie->id_ci + ISAKMP_GEN_SZ, ie->id_ci_sz - ISAKMP_GEN_SZ));

		idp = TAILQ_NEXT(idp, link);
		ie->id_cr_sz = GET_ISAKMP_GEN_LENGTH(idp->p);
		ie->id_cr = malloc(ie->id_cr_sz);
		if (!ie->id_cr) {
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "malloc (%lu) failed",
			    (unsigned long)ie->id_cr_sz);
			return -1;
		}
		memcpy(ie->id_cr, idp->p, ie->id_cr_sz);
		idp->flags |= PL_MARK;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "initiator_recv_HASH_SA_NONCE: IDcr",
		    ie->id_cr + ISAKMP_GEN_SZ, ie->id_cr_sz - ISAKMP_GEN_SZ));
	} else {
		/*
		 * If client identifiers are not present in the exchange,
		 * we fake them. RFC 2409 states:
		 *    The identities of the SAs negotiated in Quick Mode are
		 *    implicitly assumed to be the IP addresses of the ISAKMP
		 *    peers, without any constraints on the protocol or port
		 *    numbers allowed, unless client identifiers are specified
		 *    in Quick Mode.
		 *
		 * -- Michael Paddon (mwp@aba.net.au)
		 */

		ie->flags = IPSEC_EXCH_FLAG_NO_ID;

		/* Get initiator and responder addresses.  */
		msg->transport->vtbl->get_src(msg->transport, &src);
		msg->transport->vtbl->get_dst(msg->transport, &dst);
		ie->id_ci_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(src);
		ie->id_cr_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(dst);
		ie->id_ci = calloc(ie->id_ci_sz, sizeof(char));
		ie->id_cr = calloc(ie->id_cr_sz, sizeof(char));

		if (!ie->id_ci || !ie->id_cr) {
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "calloc (%lu, %lu) failed",
			    (unsigned long)ie->id_cr_sz,
			    (unsigned long)sizeof(char));
			free(ie->id_ci);
			ie->id_ci = 0;
			free(ie->id_cr);
			ie->id_cr = 0;
			return -1;
		}
		if (src->sa_family != dst->sa_family) {
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "sa_family mismatch");
			free(ie->id_ci);
			ie->id_ci = 0;
			free(ie->id_cr);
			ie->id_cr = 0;
			return -1;
		}
		switch (src->sa_family) {
		case AF_INET:
			SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV4_ADDR);
			SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV4_ADDR);
			break;

		case AF_INET6:
			SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV6_ADDR);
			SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV6_ADDR);
			break;

		default:
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "unknown sa_family %d", src->sa_family);
			free(ie->id_ci);
			ie->id_ci = 0;
			free(ie->id_cr);
			ie->id_cr = 0;
			return -1;
		}
		memcpy(ie->id_ci + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src),
		    sockaddr_addrlen(src));
		memcpy(ie->id_cr + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(dst),
		    sockaddr_addrlen(dst));
	}

	/* Build the protection suite in our SA.  */
	TAILQ_FOREACH(xf, &msg->payload[ISAKMP_PAYLOAD_TRANSFORM], link) {
		/*
		 * XXX We could check that the proposal each transform
		 * belongs to is unique.
		 */

		if (sa_add_transform(sa, xf, exchange->initiator, &proto))
			return -1;

		/* XXX Check that the chosen transform matches an offer.  */

		ipsec_decode_transform(msg, sa, proto, xf->p);
	}

	/* Now remove offers that we don't need anymore.  */
	for (proto = TAILQ_FIRST(&sa->protos); proto; proto = next_proto) {
		next_proto = TAILQ_NEXT(proto, link);
		if (!proto->chosen)
			proto_free(proto);
	}

	if (!check_policy(exchange, sa, msg->isakmp_sa)) {
		message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
		log_print("initiator_recv_HASH_SA_NONCE: policy check failed");
		return -1;
	}

	/* Mark the SA as handled.  */
	sa_p->flags |= PL_MARK;

	isa = sa->data;
	if ((isa->group_desc &&
	    (!ie->group || ie->group->id != isa->group_desc)) ||
	    (!isa->group_desc && ie->group)) {
		log_print("initiator_recv_HASH_SA_NONCE: disagreement on PFS");
		return -1;
	}
	/* Copy out the initiator's nonce.  */
	if (exchange_save_nonce(msg))
		return -1;

	/* Handle the optional KEY_EXCH payload.  */
	if (kep && ipsec_save_g_x(msg))
		return -1;

	return 0;
}

static int
initiator_send_HASH(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct prf     *prf;
	u_int8_t       *buf;
	struct hash    *hash = hash_get(isa->hash);

	/*
	 * We want a HASH payload to start with.  XXX Share with
	 * ike_main_mode.c?
	 */
	buf = malloc(ISAKMP_HASH_SZ + hash->hashsize);
	if (!buf) {
		log_error("initiator_send_HASH: malloc (%lu) failed",
		    ISAKMP_HASH_SZ + (unsigned long)hash->hashsize);
		return -1;
	}
	if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf,
	    ISAKMP_HASH_SZ + hash->hashsize, 1)) {
		free(buf);
		return -1;
	}
	/* Allocate the prf and start calculating our HASH(3).  XXX Share?  */
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_send_HASH: SKEYID_a",
	    isa->skeyid_a, isa->skeyid_len));
	prf = prf_alloc(isa->prf_type, isa->hash, isa->skeyid_a,
	    isa->skeyid_len);
	if (!prf)
		return -1;
	prf->Init(prf->prfctx);
	prf->Update(prf->prfctx, (unsigned char *)"\0", 1);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_send_HASH: message_id",
	    exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
	prf->Update(prf->prfctx, exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_send_HASH: NONCE_I_b",
	    exchange->nonce_i, exchange->nonce_i_len));
	prf->Update(prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_send_HASH: NONCE_R_b",
	    exchange->nonce_r, exchange->nonce_r_len));
	prf->Update(prf->prfctx, exchange->nonce_r, exchange->nonce_r_len);
	prf->Final(buf + ISAKMP_GEN_SZ, prf->prfctx);
	prf_free(prf);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_send_HASH: HASH(3)",
	    buf + ISAKMP_GEN_SZ, hash->hashsize));

	if (ie->group)
		message_register_post_send(msg, gen_g_xy);

	message_register_post_send(msg, post_quick_mode);

	return 0;
}

static void
post_quick_mode(struct message *msg)
{
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	struct prf     *prf;
	struct sa      *sa;
	struct proto   *proto;
	struct ipsec_proto *iproto;
	u_int8_t       *keymat;
	int             i;

	/*
	 * Loop over all SA negotiations and do both an in- and an outgoing SA
	 * per protocol.
         */
	for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
	    sa = TAILQ_NEXT(sa, next)) {
		for (proto = TAILQ_FIRST(&sa->protos); proto;
		    proto = TAILQ_NEXT(proto, link)) {
			if (proto->proto == IPSEC_PROTO_IPCOMP)
				continue;

			iproto = proto->data;

			/*
			 * There are two SAs for each SA negotiation,
			 * incoming and outgoing.
			 */
			for (i = 0; i < 2; i++) {
				prf = prf_alloc(isa->prf_type, isa->hash,
				    isa->skeyid_d, isa->skeyid_len);
				if (!prf) {
					/* XXX What to do?  */
					continue;
				}
				ie->keymat_len = ipsec_keymat_length(proto);

				/*
				 * We need to roundup the length of the key
				 * material buffer to a multiple of the PRF's
				 * blocksize as it is generated in chunks of
				 * that blocksize.
				 */
				iproto->keymat[i]
					= malloc(((ie->keymat_len + prf->blocksize - 1)
					/ prf->blocksize) * prf->blocksize);
				if (!iproto->keymat[i]) {
					log_error("post_quick_mode: "
					    "malloc (%lu) failed",
					    (((unsigned long)ie->keymat_len +
						prf->blocksize - 1) / prf->blocksize) *
					    prf->blocksize);
					/* XXX What more to do?  */
					free(prf);
					continue;
				}
				for (keymat = iproto->keymat[i];
				keymat < iproto->keymat[i] + ie->keymat_len;
				    keymat += prf->blocksize) {
					prf->Init(prf->prfctx);

					if (keymat != iproto->keymat[i]) {
						/*
						 * Hash in last round's
						 * KEYMAT.
						 */
						LOG_DBG_BUF((LOG_NEGOTIATION,
						    90, "post_quick_mode: "
						    "last KEYMAT",
						    keymat - prf->blocksize,
						    prf->blocksize));
						prf->Update(prf->prfctx,
						    keymat - prf->blocksize,
						    prf->blocksize);
					}
					/* If PFS is used hash in g^xy.  */
					if (ie->g_xy) {
						LOG_DBG_BUF((LOG_NEGOTIATION,
						    90, "post_quick_mode: "
						    "g^xy", ie->g_xy,
						    ie->g_x_len));
						prf->Update(prf->prfctx,
						    ie->g_xy, ie->g_x_len);
					}
					LOG_DBG((LOG_NEGOTIATION, 90,
					    "post_quick_mode: "
					    "suite %d proto %d", proto->no,
					    proto->proto));
					prf->Update(prf->prfctx, &proto->proto,
					    1);
					LOG_DBG_BUF((LOG_NEGOTIATION, 90,
					    "post_quick_mode: SPI",
					    proto->spi[i], proto->spi_sz[i]));
					prf->Update(prf->prfctx,
					    proto->spi[i], proto->spi_sz[i]);
					LOG_DBG_BUF((LOG_NEGOTIATION, 90,
					    "post_quick_mode: Ni_b",
					    exchange->nonce_i,
					    exchange->nonce_i_len));
					prf->Update(prf->prfctx,
					    exchange->nonce_i,
					    exchange->nonce_i_len);
					LOG_DBG_BUF((LOG_NEGOTIATION, 90,
					    "post_quick_mode: Nr_b",
					    exchange->nonce_r,
					    exchange->nonce_r_len));
					prf->Update(prf->prfctx,
					    exchange->nonce_r,
					    exchange->nonce_r_len);
					prf->Final(keymat, prf->prfctx);
				}
				prf_free(prf);
				LOG_DBG_BUF((LOG_NEGOTIATION, 90,
				    "post_quick_mode: KEYMAT",
				    iproto->keymat[i], ie->keymat_len));
			}
		}
	}

	log_verbose("isakmpd: quick mode done: %s",
	    !msg->isakmp_sa || !msg->isakmp_sa->transport ? "<no transport>"
	    : msg->isakmp_sa->transport->vtbl->decode_ids
	    (msg->isakmp_sa->transport));
}

/*
 * Accept a set of transforms offered by the initiator and chose one we can
 * handle.
 * XXX Describe in more detail.
 */
static int
responder_recv_HASH_SA_NONCE(struct message *msg)
{
	struct payload *hashp, *kep, *idp;
	struct sa      *sa;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	struct prf     *prf;
	u_int8_t       *hash, *my_hash = 0;
	size_t          hash_len;
	u_int8_t       *pkt = msg->iov[0].iov_base;
	u_int8_t        group_desc = 0;
	int             retval = -1;
	struct proto   *proto;
	struct sockaddr *src, *dst;
	char           *name;

	hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
	hash = hashp->p;
	hashp->flags |= PL_MARK;

	/* The HASH payload should be the first one.  */
	if (hash != pkt + ISAKMP_HDR_SZ) {
		/* XXX Is there a better notification type?  */
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		goto cleanup;
	}
	hash_len = GET_ISAKMP_GEN_LENGTH(hash);
	my_hash = malloc(hash_len - ISAKMP_GEN_SZ);
	if (!my_hash) {
		log_error("responder_recv_HASH_SA_NONCE: malloc (%lu) failed",
		    (unsigned long)hash_len - ISAKMP_GEN_SZ);
		goto cleanup;
	}
	/*
	 * Check the payload's integrity.
	 * XXX Share with ipsec_fill_in_hash?
         */
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_recv_HASH_SA_NONCE: "
	    "SKEYID_a", isa->skeyid_a, isa->skeyid_len));
	prf = prf_alloc(isa->prf_type, isa->hash, isa->skeyid_a,
	    isa->skeyid_len);
	if (!prf)
		goto cleanup;
	prf->Init(prf->prfctx);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90,
	    "responder_recv_HASH_SA_NONCE: message_id",
	    exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
	prf->Update(prf->prfctx, exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90,
	    "responder_recv_HASH_SA_NONCE: message after HASH",
	    hash + hash_len,
	    msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len));
	prf->Update(prf->prfctx, hash + hash_len,
	    msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len);
	prf->Final(my_hash, prf->prfctx);
	prf_free(prf);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90,
	    "responder_recv_HASH_SA_NONCE: computed HASH(1)", my_hash,
	    hash_len - ISAKMP_GEN_SZ));
	if (memcmp(hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ)
	    != 0) {
		message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0,
		    1, 0);
		goto cleanup;
	}
	free(my_hash);
	my_hash = 0;

	/* Mark message as authenticated. */
	msg->flags |= MSG_AUTHENTICATED;

	kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH);
	if (kep)
		ie->pfs = 1;

	/* Handle optional client ID payloads.  */
	idp = payload_first(msg, ISAKMP_PAYLOAD_ID);
	if (idp) {
		/* If IDci is there, IDcr must be too.  */
		if (!TAILQ_NEXT(idp, link)) {
			/* XXX Is this a good notify type?  */
			message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0,
			    1, 0);
			goto cleanup;
		}
		ie->id_ci_sz = GET_ISAKMP_GEN_LENGTH(idp->p);
		ie->id_ci = malloc(ie->id_ci_sz);
		if (!ie->id_ci) {
			log_error("responder_recv_HASH_SA_NONCE: "
			    "malloc (%lu) failed",
			    (unsigned long)ie->id_ci_sz);
			goto cleanup;
		}
		memcpy(ie->id_ci, idp->p, ie->id_ci_sz);
		idp->flags |= PL_MARK;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "responder_recv_HASH_SA_NONCE: IDci",
		    ie->id_ci + ISAKMP_GEN_SZ, ie->id_ci_sz - ISAKMP_GEN_SZ));

		idp = TAILQ_NEXT(idp, link);
		ie->id_cr_sz = GET_ISAKMP_GEN_LENGTH(idp->p);
		ie->id_cr = malloc(ie->id_cr_sz);
		if (!ie->id_cr) {
			log_error("responder_recv_HASH_SA_NONCE: "
			    "malloc (%lu) failed",
			    (unsigned long)ie->id_cr_sz);
			goto cleanup;
		}
		memcpy(ie->id_cr, idp->p, ie->id_cr_sz);
		idp->flags |= PL_MARK;
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "responder_recv_HASH_SA_NONCE: IDcr",
		    ie->id_cr + ISAKMP_GEN_SZ, ie->id_cr_sz - ISAKMP_GEN_SZ));
	} else {
		/*
		 * If client identifiers are not present in the exchange,
		 * we fake them. RFC 2409 states:
		 *    The identities of the SAs negotiated in Quick Mode are
		 *    implicitly assumed to be the IP addresses of the ISAKMP
		 *    peers, without any constraints on the protocol or port
		 *    numbers allowed, unless client identifiers are specified
		 *    in Quick Mode.
		 *
		 * -- Michael Paddon (mwp@aba.net.au)
		 */

		ie->flags = IPSEC_EXCH_FLAG_NO_ID;

		/* Get initiator and responder addresses.  */
		msg->transport->vtbl->get_src(msg->transport, &src);
		msg->transport->vtbl->get_dst(msg->transport, &dst);
		ie->id_ci_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(src);
		ie->id_cr_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(dst);
		ie->id_ci = calloc(ie->id_ci_sz, sizeof(char));
		ie->id_cr = calloc(ie->id_cr_sz, sizeof(char));

		if (!ie->id_ci || !ie->id_cr) {
			log_error("responder_recv_HASH_SA_NONCE: "
			    "calloc (%lu, %lu) failed",
			    (unsigned long)ie->id_ci_sz,
			    (unsigned long)sizeof(char));
			goto cleanup;
		}
		if (src->sa_family != dst->sa_family) {
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "sa_family mismatch");
			goto cleanup;
		}
		switch (src->sa_family) {
		case AF_INET:
			SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV4_ADDR);
			SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV4_ADDR);
			break;

		case AF_INET6:
			SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV6_ADDR);
			SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV6_ADDR);
			break;

		default:
			log_error("initiator_recv_HASH_SA_NONCE: "
			    "unknown sa_family %d", src->sa_family);
			goto cleanup;
		}

		memcpy(ie->id_cr + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src),
		    sockaddr_addrlen(src));
		memcpy(ie->id_ci + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(dst),
		    sockaddr_addrlen(dst));
	}

	if (message_negotiate_sa(msg, check_policy))
		goto cleanup;

	for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
	    sa = TAILQ_NEXT(sa, next)) {
		for (proto = TAILQ_FIRST(&sa->protos); proto;
		    proto = TAILQ_NEXT(proto, link)) {
			/*
			 * XXX we need to have some attributes per proto, not
			 * all per SA.
			 */
			ipsec_decode_transform(msg, sa, proto,
			    proto->chosen->p);
			if (proto->proto == IPSEC_PROTO_IPSEC_AH &&
			    !((struct ipsec_proto *)proto->data)->auth) {
				log_print("responder_recv_HASH_SA_NONCE: "
				    "AH proposed without an algorithm "
				    "attribute");
				message_drop(msg,
				    ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
				goto next_sa;
			}
		}

		isa = sa->data;

		/*
		 * The group description is mandatory if we got a KEY_EXCH
		 * payload.
		 */
		if (kep) {
			if (!isa->group_desc) {
				log_print("responder_recv_HASH_SA_NONCE: "
				    "KEY_EXCH payload without a group "
				    "desc. attribute");
				message_drop(msg,
				    ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
				continue;
			}
			/* Also, all SAs must have equal groups.  */
			if (!group_desc)
				group_desc = isa->group_desc;
			else if (group_desc != isa->group_desc) {
				log_print("responder_recv_HASH_SA_NONCE: "
				  "differing group descriptions in one QM");
				message_drop(msg,
				    ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
				continue;
			}
		}
		/* At least one SA was accepted.  */
		retval = 0;

next_sa:
		;	/* XXX gcc3 wants this. */
	}

	if (kep) {
		ie->group = group_get(group_desc);
		if (!ie->group) {
			/*
			 * XXX If the error was due to an out-of-range group
			 * description we should notify our peer, but this
			 * should probably be done by the attribute
			 * validation.  Is it?
			 */
			goto cleanup;
		}
	}
	/* Copy out the initiator's nonce.  */
	if (exchange_save_nonce(msg))
		goto cleanup;

	/* Handle the optional KEY_EXCH payload.  */
	if (kep && ipsec_save_g_x(msg))
		goto cleanup;

	/*
	 * Try to find and set the connection name on the exchange.
         */

	/*
	 * Check for accepted identities as well as lookup the connection
	 * name and set it on the exchange.
	 *
	 * When not using policies make sure the peer proposes sane IDs.
	 * Otherwise this is done by KeyNote.
         */
	name = connection_passive_lookup_by_ids(ie->id_ci, ie->id_cr);
	if (name) {
		exchange->name = strdup(name);
		if (!exchange->name) {
			log_error("responder_recv_HASH_SA_NONCE: "
			    "strdup (\"%s\") failed", name);
			goto cleanup;
		}
	} else if (
	    ignore_policy ||
	    strncmp("yes", conf_get_str("General", "Use-Keynote"), 3)) {
		log_print("responder_recv_HASH_SA_NONCE: peer proposed "
		    "invalid phase 2 IDs: %s",
		    (exchange->doi->decode_ids("initiator id %s, responder"
		    " id %s", ie->id_ci, ie->id_ci_sz, ie->id_cr,
		    ie->id_cr_sz, 1)));
		message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION, 0, 1,
		    0);
		goto cleanup;
	}

	return retval;

cleanup:
	/* Remove all potential protocols that have been added to the SAs.  */
	for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
	    sa = TAILQ_NEXT(sa, next))
		while ((proto = TAILQ_FIRST(&sa->protos)) != 0)
			proto_free(proto);
	free(my_hash);
	free(ie->id_ci);
	ie->id_ci = 0;
	free(ie->id_cr);
	ie->id_cr = 0;
	return -1;
}

/* Reply with the transform we chose.  */
static int
responder_send_HASH_SA_NONCE(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct prf     *prf;
	struct hash    *hash = hash_get(isa->hash);
	size_t          nonce_sz = exchange->nonce_i_len;
	u_int8_t       *buf;
	int             initiator = exchange->initiator;
	char            header[80];
	u_int32_t       i;
	u_int8_t       *id;
	size_t          sz;

	/*
	 * We want a HASH payload to start with.  XXX Share with
	 * ike_main_mode.c?
	 */
	buf = malloc(ISAKMP_HASH_SZ + hash->hashsize);
	if (!buf) {
		log_error("responder_send_HASH_SA_NONCE: malloc (%lu) failed",
			  ISAKMP_HASH_SZ + (unsigned long)hash->hashsize);
		return -1;
	}
	if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf,
	    ISAKMP_HASH_SZ + hash->hashsize, 1)) {
		free(buf);
		return -1;
	}
	/* Add the SA payload(s) with the transform(s) that was/were chosen. */
	if (message_add_sa_payload(msg))
		return -1;

	/* Generate a nonce, and add it to the message.  */
	if (exchange_gen_nonce(msg, nonce_sz))
		return -1;

	/* Generate optional KEY_EXCH payload.  This is known as PFS.  */
	if (ie->group && ipsec_gen_g_x(msg))
		return -1;

	/*
	 * If the initiator client ID's were acceptable, just mirror them
	 * back.
	 */
	if (!(ie->flags & IPSEC_EXCH_FLAG_NO_ID)) {
		sz = ie->id_ci_sz;
		id = malloc(sz);
		if (!id) {
			log_error("responder_send_HASH_SA_NONCE: "
			    "malloc (%lu) failed", (unsigned long)sz);
			return -1;
		}
		memcpy(id, ie->id_ci, sz);
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "responder_send_HASH_SA_NONCE: IDic", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
		sz = ie->id_cr_sz;
		id = malloc(sz);
		if (!id) {
			log_error("responder_send_HASH_SA_NONCE: "
			    "malloc (%lu) failed", (unsigned long)sz);
			return -1;
		}
		memcpy(id, ie->id_cr, sz);
		LOG_DBG_BUF((LOG_NEGOTIATION, 90,
		    "responder_send_HASH_SA_NONCE: IDrc", id, sz));
		if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) {
			free(id);
			return -1;
		}
	}
	/* Allocate the prf and start calculating our HASH(2).  XXX Share?  */
	LOG_DBG((LOG_NEGOTIATION, 90, "responder_recv_HASH: "
	    "isakmp_sa %p isa %p", isakmp_sa, isa));
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_send_HASH_SA_NONCE: "
	    "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_NEGOTIATION, 90,
	    "responder_send_HASH_SA_NONCE: message_id",
	    exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
	prf->Update(prf->prfctx, exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_send_HASH_SA_NONCE: "
	    "NONCE_I_b", exchange->nonce_i, exchange->nonce_i_len));
	prf->Update(prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);

	/* Loop over all payloads after HASH(2).  */
	for (i = 2; i < msg->iovlen; i++) {
		/* XXX Misleading payload type printouts.  */
		snprintf(header, sizeof header,
		   "responder_send_HASH_SA_NONCE: payload %d after HASH(2)",
			 i - 1);
		LOG_DBG_BUF((LOG_NEGOTIATION, 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);
	snprintf(header, sizeof header, "responder_send_HASH_SA_NONCE: "
	    "HASH_%c", initiator ? 'I' : 'R');
	LOG_DBG_BUF((LOG_NEGOTIATION, 80, header, buf + ISAKMP_HASH_DATA_OFF,
	    hash->hashsize));

	if (ie->group)
		message_register_post_send(msg, gen_g_xy);

	return 0;
}

static void
gen_g_xy(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;

	/* Compute Diffie-Hellman shared value.  */
	ie->g_xy = malloc(ie->g_x_len);
	if (!ie->g_xy) {
		log_error("gen_g_xy: malloc (%lu) failed",
		    (unsigned long)ie->g_x_len);
		return;
	}
	if (dh_create_shared(ie->group, ie->g_xy,
	    exchange->initiator ? ie->g_xr : ie->g_xi)) {
		log_print("gen_g_xy: dh_create_shared failed");
		return;
	}
	LOG_DBG_BUF((LOG_NEGOTIATION, 80, "gen_g_xy: g^xy", ie->g_xy,
	    ie->g_x_len));
}

static int
responder_recv_HASH(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct ipsec_sa *isa = isakmp_sa->data;
	struct prf     *prf;
	u_int8_t       *hash, *my_hash = 0;
	size_t          hash_len;
	struct payload *hashp;

	/* Find HASH(3) and create our own hash, just as big.  */
	hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
	hash = hashp->p;
	hashp->flags |= PL_MARK;
	hash_len = GET_ISAKMP_GEN_LENGTH(hash);
	my_hash = malloc(hash_len - ISAKMP_GEN_SZ);
	if (!my_hash) {
		log_error("responder_recv_HASH: malloc (%lu) failed",
			  (unsigned long)hash_len - ISAKMP_GEN_SZ);
		goto cleanup;
	}
	/* Allocate the prf and start calculating our HASH(3).  XXX Share?  */
	LOG_DBG((LOG_NEGOTIATION, 90, "responder_recv_HASH: "
	    "isakmp_sa %p isa %p", isakmp_sa, isa));
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_recv_HASH: SKEYID_a",
	    isa->skeyid_a, isa->skeyid_len));
	prf = prf_alloc(isa->prf_type, isa->hash, isa->skeyid_a,
	    isa->skeyid_len);
	if (!prf)
		goto cleanup;
	prf->Init(prf->prfctx);
	prf->Update(prf->prfctx, (unsigned char *)"\0", 1);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_recv_HASH: message_id",
	    exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
	prf->Update(prf->prfctx, exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_recv_HASH: NONCE_I_b",
	    exchange->nonce_i, exchange->nonce_i_len));
	prf->Update(prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90, "responder_recv_HASH: NONCE_R_b",
	    exchange->nonce_r, exchange->nonce_r_len));
	prf->Update(prf->prfctx, exchange->nonce_r, exchange->nonce_r_len);
	prf->Final(my_hash, prf->prfctx);
	prf_free(prf);
	LOG_DBG_BUF((LOG_NEGOTIATION, 90,
	    "responder_recv_HASH: computed HASH(3)", my_hash,
	    hash_len - ISAKMP_GEN_SZ));
	if (memcmp(hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ)
	    != 0) {
		message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0,
		    1, 0);
		goto cleanup;
	}
	free(my_hash);

	/* Mark message as authenticated. */
	msg->flags |= MSG_AUTHENTICATED;

	post_quick_mode(msg);

	return 0;

cleanup:
	free(my_hash);
	return -1;
}