root/sbin/isakmpd/ike_quick_mode.c
/* $OpenBSD: ike_quick_mode.c,v 1.115 2023/03/31 20:16:55 tb 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 "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;
        char           *name;
        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 = reallocarray(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 = reallocarray(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 = reallocarray(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 = reallocarray(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 = reallocarray(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);
                                }

                                if (proto_id == IPSEC_PROTO_IPSEC_ESP &&
                                    (exchange->flags &
                                    EXCHANGE_FLAG_NAT_T_ENABLE)) {
                                        name = conf_get_str(xf->field,
                                            "ENCAPSULATION_MODE");
                                        if (name) {
                                                value = constant_value(
                                                    ipsec_encap_cst,
                                                    name);
                                                switch (value) {
                                                case IPSEC_ENCAP_TUNNEL:
                                                        value = exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT ?
                                                            IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT :
                                                            IPSEC_ENCAP_UDP_ENCAP_TUNNEL;
                                                        break;
                                                case IPSEC_ENCAP_TRANSPORT:
                                                        value = exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT ?
                                                            IPSEC_ENCAP_UDP_ENCAP_TRANSPORT_DRAFT :
                                                            IPSEC_ENCAP_UDP_ENCAP_TRANSPORT;
                                                        break;
                                                }
                                                attr = attribute_set_basic(
                                                    attr,
                                                    IPSEC_ATTR_ENCAPSULATION_MODE,
                                                    value);
                                        }
                                } else {
                                        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) {
                                        free(proto);
                                        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 = calloc(1, sizeof *pa);
                                if (!pa) {
                                        free(proto->data);
                                        free(proto);
                                        goto bail_out;
                                }
                                pa->len = transform_len[prop_no][xf_no];
                                pa->attrs = malloc(pa->len);
                                if (!pa->attrs) {
                                        free(proto->data);
                                        free(proto);
                                        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);
                if (!ie->group)
                        return -1;
                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;

        /* Drop message when it contains ID types we do not implement yet.  */
        TAILQ_FOREACH(idp, &msg->payload[ISAKMP_PAYLOAD_ID], link) {
                switch (GET_ISAKMP_ID_TYPE(idp->p)) {
                case IPSEC_ID_IPV4_ADDR:
                case IPSEC_ID_IPV4_ADDR_SUBNET:
                case IPSEC_ID_IPV6_ADDR:
                case IPSEC_ID_IPV6_ADDR_SUBNET:
                        break;

                case IPSEC_ID_FQDN:
                        /*
                         * FQDN may be used for in NAT-T with transport mode.
                         * We can handle the message in this case.  In the
                         * other cases we'll drop the message later.
                         */
                        break;

                default:
                        message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
                            0, 1, 0);
                        return -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_xy_len));
                                                prf->Update(prf->prfctx,
                                                    ie->g_xy, ie->g_xy_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: %s",
            (exchange->initiator == 0) ? " (as responder)" : "",
            !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;

        /* Drop message when it contains ID types we do not implement yet.  */
        TAILQ_FOREACH(idp, &msg->payload[ISAKMP_PAYLOAD_ID], link) {
                switch (GET_ISAKMP_ID_TYPE(idp->p)) {
                case IPSEC_ID_IPV4_ADDR:
                case IPSEC_ID_IPV4_ADDR_SUBNET:
                case IPSEC_ID_IPV6_ADDR:
                case IPSEC_ID_IPV6_ADDR_SUBNET:
                        break;

                case IPSEC_ID_FQDN:
                        /*
                         * FQDN may be used for in NAT-T with transport mode.
                         * We can handle the message in this case.  In the
                         * other cases we'll drop the message later.
                         */
                        break;

                default:
                        message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
                            0, 1, 0);
                        goto cleanup;
                }
        }

        /* 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_len = dh_secretlen(ie->group);
        ie->g_xy = malloc(ie->g_xy_len);
        if (!ie->g_xy) {
                log_error("gen_g_xy: malloc (%lu) failed",
                    (unsigned long)ie->g_xy_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_xy_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;
}