root/sbin/isakmpd/isakmp_cfg.c
/* $OpenBSD: isakmp_cfg.c,v 1.41 2018/01/15 09:54:48 mpi Exp $   */

/*
 * Copyright (c) 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 2002 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 Gatespace
 * (http://www.gatespace.com/).
 */

#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <bitstring.h>

#include "attribute.h"
#include "conf.h"
#include "exchange.h"
#include "hash.h"
#include "ipsec.h"
#include "isakmp_fld.h"
#include "isakmp_num.h"
#include "log.h"
#include "message.h"
#include "prf.h"
#include "sa.h"
#include "transport.h"
#include "util.h"

/*
 * Validation script used to test messages for correct content of
 * payloads depending on the exchange type.
 */
int16_t script_transaction[] = {
        ISAKMP_PAYLOAD_ATTRIBUTE,       /* Initiator -> responder.  */
        EXCHANGE_SCRIPT_SWITCH,
        ISAKMP_PAYLOAD_ATTRIBUTE,       /* Responder -> initiator.  */
        EXCHANGE_SCRIPT_END
};

static int      cfg_decode_attribute(u_int16_t, u_int8_t *, u_int16_t, void *);
static int      cfg_encode_attributes(struct isakmp_cfg_attr_head *, u_int32_t,
    u_int32_t, char *, u_int8_t **, u_int16_t *);
static int      cfg_initiator_send_ATTR(struct message *);
static int      cfg_initiator_recv_ATTR(struct message *);
static int      cfg_responder_recv_ATTR(struct message *);
static int      cfg_responder_send_ATTR(struct message *);

u_int8_t       *cfg_add_hash(struct message *);
int             cfg_finalize_hash(struct message *, u_int8_t *, u_int8_t *,
    u_int16_t);
int             cfg_verify_hash(struct message *);

/* Server: SET/ACK    Client; REQ/REPLY */
int (*isakmp_cfg_initiator[]) (struct message *) = {
        cfg_initiator_send_ATTR,
        cfg_initiator_recv_ATTR
};

/* Server: REQ/REPLY  Client: SET/ACK */
int (*isakmp_cfg_responder[]) (struct message *) = {
        cfg_responder_recv_ATTR,
        cfg_responder_send_ATTR
};

/*
 * When we are "the server", this starts SET/ACK mode
 * When we are "the client", this starts REQ/REPLY mode
 */
static int
cfg_initiator_send_ATTR(struct message *msg)
{
        struct sa      *isakmp_sa = msg->isakmp_sa;
        struct ipsec_exch *ie = msg->exchange->data;
        u_int8_t       *hashp = 0, *attrp, *attr;
        size_t          attrlen, off;
        char           *id_string, *cfg_mode, *field;
        struct sockaddr *sa;
#define CFG_ATTR_BIT_MAX ISAKMP_CFG_ATTR_FUTURE_MIN     /* XXX */
        bitstr_t        bit_decl(attrbits, CFG_ATTR_BIT_MAX);
        u_int16_t       bit, length;
        u_int32_t       life;

        if (msg->exchange->phase == 2) {
                hashp = cfg_add_hash(msg);
                if (!hashp)
                        return -1;
        }
        /* We initiated this exchange, check isakmp_sa for other side.  */
        if (isakmp_sa->initiator)
                id_string = ipsec_id_string(isakmp_sa->id_r,
                    isakmp_sa->id_r_len);
        else
                id_string = ipsec_id_string(isakmp_sa->id_i,
                    isakmp_sa->id_i_len);
        if (!id_string) {
                log_print("cfg_initiator_send_ATTR: cannot parse ID");
                goto fail;
        }
        /* Check for attribute list to send to the other side */
        attrlen = 0;
        bit_nclear(attrbits, 0, CFG_ATTR_BIT_MAX - 1);

        cfg_mode = conf_get_str(id_string, "Mode");
        if (!cfg_mode || strcmp(cfg_mode, "SET") == 0) {
                /* SET/ACK mode */
                ie->cfg_type = ISAKMP_CFG_SET;

                LOG_DBG((LOG_NEGOTIATION, 10,
                    "cfg_initiator_send_ATTR: SET/ACK mode"));

#define ATTRFIND(STR,ATTR4,LEN4,ATTR6,LEN6) do                          \
        {                                                               \
                if ((sa = conf_get_address (id_string, STR)) != NULL)   \
                        switch (sa->sa_family) {                        \
                        case AF_INET:                                   \
                                bit_set (attrbits, ATTR4);              \
                                attrlen += ISAKMP_ATTR_SZ + LEN4;       \
                                break;                                  \
                        case AF_INET6:                                  \
                                bit_set (attrbits, ATTR6);              \
                                attrlen += ISAKMP_ATTR_SZ + LEN6;       \
                                break;                                  \
                        default:                                        \
                                break;                                  \
                        }                                               \
                free (sa);                                              \
        } while (0)

                /*
                 * XXX We don't simultaneously support IPv4 and IPv6
                 * addresses.
                 */
                ATTRFIND("Address", ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS, 4,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS, 16);
                ATTRFIND("Netmask", ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK, 4,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK, 16);
                ATTRFIND("Nameserver", ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS, 4,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS, 16);
                ATTRFIND("WINS-server", ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS, 4,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS, 16);
                ATTRFIND("DHCP-server", ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP, 4,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP, 16);
#ifdef notyet
                ATTRFIND("Network", ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET, 8,
                    ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET, 17);
#endif
#undef ATTRFIND

                if (conf_get_str(id_string, "Lifetime")) {
                        bit_set(attrbits,
                            ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY);
                        attrlen += ISAKMP_ATTR_SZ + 4;
                }
        } else {
                struct conf_list *alist;
                struct conf_list_node *anode;

                ie->cfg_type = ISAKMP_CFG_REQUEST;

                LOG_DBG((LOG_NEGOTIATION, 10,
                    "cfg_initiator_send_ATTR: REQ/REPLY mode"));

                alist = conf_get_list(id_string, "Attributes");
                if (alist) {
                        for (anode = TAILQ_FIRST(&alist->fields); anode;
                            anode = TAILQ_NEXT(anode, link)) {
                                if (strcasecmp(anode->field, "Address") == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS);
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS);
                                        attrlen += ISAKMP_ATTR_SZ * 2;
                                } else if (strcasecmp(anode->field, "Netmask")
                                    == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK);
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK);
                                        attrlen += ISAKMP_ATTR_SZ * 2;
                                } else if (strcasecmp(anode->field,
                                    "Nameserver") == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS);
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS);
                                        attrlen += ISAKMP_ATTR_SZ * 2;
                                } else if (strcasecmp(anode->field,
                                    "WINS-server") == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS);
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS);
                                        attrlen += ISAKMP_ATTR_SZ * 2;
                                } else if (strcasecmp(anode->field,
                                    "DHCP-server") == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP);
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP);
                                        attrlen += ISAKMP_ATTR_SZ * 2;
                                } else if (strcasecmp(anode->field,
                                    "Lifetime") == 0) {
                                        bit_set(attrbits, ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY);
                                        attrlen += ISAKMP_ATTR_SZ;
                                } else {
                                        log_print("cfg_initiator_send_ATTR: "
                                            "unknown attribute %.20s in "
                                            "section [%s]", anode->field,
                                            id_string);
                                }
                        }

                        conf_free_list(alist);
                }
        }

        if (attrlen == 0) {
                /* No data found.  */
                log_print("cfg_initiator_send_ATTR: no IKECFG attributes "
                    "found for [%s]", id_string);

                /*
                 * We can continue, but this indicates a configuration error
                 * that the user probably will want to correct.
                 */
                free(id_string);
                return 0;
        }
        attrlen += ISAKMP_ATTRIBUTE_SZ;
        attrp = calloc(1, attrlen);
        if (!attrp) {
                log_error("cfg_initiator_send_ATTR: calloc (1, %lu) failed",
                    (unsigned long)attrlen);
                goto fail;
        }
        if (message_add_payload(msg, ISAKMP_PAYLOAD_ATTRIBUTE, attrp, attrlen,
            1)) {
                free(attrp);
                goto fail;
        }
        SET_ISAKMP_ATTRIBUTE_TYPE(attrp, ie->cfg_type);
        arc4random_buf((u_int8_t *) & ie->cfg_id, sizeof ie->cfg_id);
        SET_ISAKMP_ATTRIBUTE_ID(attrp, ie->cfg_id);

        off = ISAKMP_ATTRIBUTE_SZ;

        /*
         * Use the bitstring built previously to collect the right
         * parameters for attrp.
         */
        for (bit = 0; bit < CFG_ATTR_BIT_MAX; bit++)
                if (bit_test(attrbits, bit)) {
                        attr = attrp + off;
                        SET_ISAKMP_ATTR_TYPE(attr, bit);

                        if (ie->cfg_type == ISAKMP_CFG_REQUEST) {
                                off += ISAKMP_ATTR_SZ;
                                continue;
                        }
                        /* All the other are similar, this is the odd one.  */
                        if (bit == ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY) {
                                life = conf_get_num(id_string, "Lifetime",
                                    1200);
                                SET_ISAKMP_ATTR_LENGTH_VALUE(attr, 4);
                                encode_32(attr + ISAKMP_ATTR_VALUE_OFF, life);
                                off += ISAKMP_ATTR_SZ + 4;
                                continue;
                        }
                        switch (bit) {
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                                length = 4;
                                break;

                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                                length = 16;
                                break;

                        default:
                                length = 0;     /* Silence gcc.  */
                        }

                        switch (bit) {
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                                field = "Address";
                                break;
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                                field = "Netmask";
                                break;
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                                field = "Nameserver";
                                break;
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                                field = "DHCP-server";
                                break;
                        case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                        case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                                field = "WINS-server";
                                break;
                        default:
                                field = 0;      /* Silence gcc.  */
                        }

                        sa = conf_get_address(id_string, field);

                        SET_ISAKMP_ATTR_LENGTH_VALUE(attr, length);
                        memcpy(attr + ISAKMP_ATTR_VALUE_OFF,
                            sockaddr_addrdata(sa), length);

                        free(sa);

                        off += ISAKMP_ATTR_SZ + length;
                }
        if (msg->exchange->phase == 2)
                if (cfg_finalize_hash(msg, hashp, attrp, attrlen))
                        goto fail;

        return 0;

fail:
        free(id_string);
        return -1;
}

/*
 * As "the server", this ends SET/ACK.
 * As "the client", this ends REQ/REPLY.
 */
static int
cfg_initiator_recv_ATTR(struct message *msg)
{
        struct payload *attrp = payload_first(msg, ISAKMP_PAYLOAD_ATTRIBUTE);
        struct ipsec_exch *ie = msg->exchange->data;
        struct sa      *isakmp_sa = msg->isakmp_sa;
        struct isakmp_cfg_attr *attr;
        struct sockaddr *sa;
        const char     *uk_addr = "<unknown>";
        char           *addr;

        if (msg->exchange->phase == 2)
                if (cfg_verify_hash(msg))
                        return -1;

        /* Sanity.  */
        if (ie->cfg_id != GET_ISAKMP_ATTRIBUTE_ID(attrp->p)) {
                log_print("cfg_initiator_recv_ATTR: "
                    "cfg packet ID does not match!");
                message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
                return -1;
        }
        switch (attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]) {
        case ISAKMP_CFG_ACK:
                if (ie->cfg_type != ISAKMP_CFG_SET) {
                        log_print("cfg_initiator_recv_ATTR: "
                            "bad packet type ACK");
                        message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
                            0, 1, 0);
                        return -1;
                }
                break;
        case ISAKMP_CFG_REPLY:
                if (ie->cfg_type != ISAKMP_CFG_REQUEST) {
                        log_print("cfg_initiator_recv_ATTR: "
                            "bad packet type REPLY");
                        message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
                            0, 1, 0);
                        return -1;
                }
                break;

        default:
                log_print("cfg_initiator_recv_ATTR: unexpected configuration "
                    "message type %d", attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]);
                message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
                return -1;
        }

        attribute_map(attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF,
            GET_ISAKMP_GEN_LENGTH(attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
            cfg_decode_attribute, ie);

        switch (ie->cfg_type) {
        case ISAKMP_CFG_ACK: {
                        /* SET/ACK -- Server side (ACK from client) */
                        msg->transport->vtbl->get_src(isakmp_sa->transport,
                            &sa);
                        if (sockaddr2text(sa, &addr, 0) < 0)
                                addr = (char *) uk_addr;

                        for (attr = LIST_FIRST(&ie->attrs); attr;
                            attr = LIST_NEXT(attr, link))
                                LOG_DBG((LOG_NEGOTIATION, 50,
                                    "cfg_initiator_recv_ATTR: "
                                    "client %s ACKs attribute %s", addr,
                                    constant_name(isakmp_cfg_attr_cst,
                                        attr->type)));

                        if (addr != uk_addr)
                                free(addr);
                }
                break;

        case ISAKMP_CFG_REPLY: {
                        /*
                         * REQ/REPLY: effect attributes we've gotten
                         * responses on.
                         */
                        msg->transport->vtbl->get_src(isakmp_sa->transport,
                            &sa);
                        if (sockaddr2text(sa, &addr, 0) < 0)
                                addr = (char *) uk_addr;

                        for (attr = LIST_FIRST(&ie->attrs); attr;
                            attr = LIST_NEXT(attr, link))
                                LOG_DBG((LOG_NEGOTIATION, 50,
                                    "cfg_initiator_recv_ATTR: "
                                    "server %s replied with attribute %s",
                                    addr, constant_name(isakmp_cfg_attr_cst,
                                        attr->type)));

                        if (addr != uk_addr)
                                free(addr);
                }
                break;

        default:
                break;
        }

        attrp->flags |= PL_MARK;
        return 0;
}

/*
 * As "the server", this starts REQ/REPLY (initiated by the client).
 * As "the client", this starts SET/ACK (initiated by the server).
 */
static int
cfg_responder_recv_ATTR(struct message *msg)
{
        struct payload *attrp = payload_first(msg, ISAKMP_PAYLOAD_ATTRIBUTE);
        struct ipsec_exch *ie = msg->exchange->data;
        struct sa      *isakmp_sa = msg->isakmp_sa;
        struct isakmp_cfg_attr *attr;
        struct sockaddr *sa;
        char           *addr;

        if (msg->exchange->phase == 2)
                if (cfg_verify_hash(msg))
                        return -1;

        ie->cfg_id = GET_ISAKMP_ATTRIBUTE_ID(attrp->p);
        ie->cfg_type = attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF];

        switch (ie->cfg_type) {
        case ISAKMP_CFG_REQUEST:
        case ISAKMP_CFG_SET:
                break;

        default:
                message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
                log_print("cfg_responder_recv_ATTR: "
                    "unexpected configuration message type %d", ie->cfg_type);
                return -1;
        }

        attribute_map(attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF,
            GET_ISAKMP_GEN_LENGTH(attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
            cfg_decode_attribute, ie);

        switch (ie->cfg_type) {
        case ISAKMP_CFG_REQUEST:
                /* We're done.  */
                break;

        case ISAKMP_CFG_SET: {
                        /* SET/ACK -- Client side (SET from server) */
                        const char     *uk_addr = "<unknown>";

                        msg->transport->vtbl->get_dst(isakmp_sa->transport,
                            &sa);
                        if (sockaddr2text(sa, &addr, 0) < 0)
                                addr = (char *) uk_addr;

                        for (attr = LIST_FIRST(&ie->attrs); attr;
                            attr = LIST_NEXT(attr, link))
                                LOG_DBG((LOG_NEGOTIATION, 50,
                                    "cfg_responder_recv_ATTR: "
                                    "server %s asks us to SET attribute %s",
                                    addr, constant_name(isakmp_cfg_attr_cst,
                                        attr->type)));

                        /*
                         * XXX Here's the place to add code to walk through
                         * XXX each attribute and send them along to dhclient
                         * XXX or whatever. Each attribute that we act upon
                         * XXX (such as setting a netmask), should be marked
                         * XXX like this for us to send the proper ACK
                         * XXX response: attr->attr_used++;
                         */

                        if (addr != uk_addr)
                                free(addr);
                }
                break;

        default:
                break;
        }

        attrp->flags |= PL_MARK;
        return 0;
}

/*
 * As "the server", this ends REQ/REPLY mode.
 * As "the client", this ends SET/ACK mode.
 */
static int
cfg_responder_send_ATTR(struct message *msg)
{
        struct ipsec_exch *ie = msg->exchange->data;
        struct sa      *isakmp_sa = msg->isakmp_sa;
        u_int8_t       *hashp = 0, *attrp;
        u_int16_t       attrlen;
        char           *id_string;

        if (msg->exchange->phase == 2) {
                hashp = cfg_add_hash(msg);
                if (!hashp)
                        return -1;
        }
        /* We are responder, check isakmp_sa for other side.  */
        if (isakmp_sa->initiator ^ (ie->cfg_type == ISAKMP_CFG_REQUEST))
                id_string = ipsec_id_string(isakmp_sa->id_i,
                    isakmp_sa->id_i_len);
        else
                id_string = ipsec_id_string(isakmp_sa->id_r,
                    isakmp_sa->id_r_len);
        if (!id_string) {
                log_print("cfg_responder_send_ATTR: cannot parse client's ID");
                return -1;
        }
        if (cfg_encode_attributes(&ie->attrs, (ie->cfg_type == ISAKMP_CFG_SET ?
            ISAKMP_CFG_ACK : ISAKMP_CFG_REPLY), ie->cfg_id, id_string, &attrp,
            &attrlen)) {
                free(id_string);
                return -1;
        }
        free(id_string);

        if (message_add_payload(msg, ISAKMP_PAYLOAD_ATTRIBUTE, attrp, attrlen,
            1)) {
                free(attrp);
                return -1;
        }
        if (msg->exchange->phase == 2)
                if (cfg_finalize_hash(msg, hashp, attrp, attrlen))
                        return -1;

        return 0;
}

u_int8_t *
cfg_add_hash(struct message *msg)
{
        struct ipsec_sa *isa = msg->isakmp_sa->data;
        struct hash    *hash = hash_get(isa->hash);
        u_int8_t       *hashp;

        hashp = malloc(ISAKMP_HASH_SZ + hash->hashsize);
        if (!hashp) {
                log_error("cfg_add_hash: malloc (%lu) failed",
                    ISAKMP_HASH_SZ + (unsigned long)hash->hashsize);
                return 0;
        }
        if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, hashp,
            ISAKMP_HASH_SZ + hash->hashsize, 1)) {
                free(hashp);
                return 0;
        }
        return hashp;
}

int
cfg_finalize_hash(struct message *msg, u_int8_t *hashp, u_int8_t *data,
    u_int16_t length)
{
        struct ipsec_sa *isa = msg->isakmp_sa->data;
        struct prf     *prf;

        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, msg->exchange->message_id,
            ISAKMP_HDR_MESSAGE_ID_LEN);
        prf->Update(prf->prfctx, data, length);
        prf->Final(hashp + ISAKMP_GEN_SZ, prf->prfctx);
        prf_free(prf);
        return 0;
}

int
cfg_verify_hash(struct message *msg)
{
        struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
        struct ipsec_sa *isa = msg->isakmp_sa->data;
        struct prf     *prf;
        u_int8_t       *hash, *comp_hash;
        size_t          hash_len;

        if (!hashp) {
                log_print("cfg_verify_hash: phase 2 message missing HASH");
                message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION,
                    0, 1, 0);
                return -1;
        }
        hash = hashp->p;
        hash_len = GET_ISAKMP_GEN_LENGTH(hash);
        comp_hash = malloc(hash_len - ISAKMP_GEN_SZ);
        if (!comp_hash) {
                log_error("cfg_verify_hash: malloc (%lu) failed",
                    (unsigned long)hash_len - ISAKMP_GEN_SZ);
                return -1;
        }
        /* Verify hash.  */
        prf = prf_alloc(isa->prf_type, isa->hash, isa->skeyid_a,
            isa->skeyid_len);
        if (!prf) {
                free(comp_hash);
                return -1;
        }
        prf->Init(prf->prfctx);
        prf->Update(prf->prfctx, msg->exchange->message_id,
            ISAKMP_HDR_MESSAGE_ID_LEN);
        prf->Update(prf->prfctx, hash + hash_len,
            msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len);
        prf->Final(comp_hash, prf->prfctx);
        prf_free(prf);

        if (memcmp(hash + ISAKMP_GEN_SZ, comp_hash, hash_len - ISAKMP_GEN_SZ)
            != 0) {
                message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION,
                    0, 1, 0);
                free(comp_hash);
                return -1;
        }
        free(comp_hash);

        /* Mark the HASH as handled.  */
        hashp->flags |= PL_MARK;

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

        return 0;
}

/*
 * Decode the attribute of type TYPE with a LEN length value pointed to by
 * VALUE.  VIE is a pointer to the IPsec exchange context holding the
 * attributes indexed by type for easy retrieval.
 */
static int
cfg_decode_attribute(u_int16_t type, u_int8_t * value, u_int16_t len,
    void *vie)
{
        struct ipsec_exch *ie = vie;
        struct isakmp_cfg_attr *attr;

        if (type >= ISAKMP_CFG_ATTR_PRIVATE_MIN &&
            type <= ISAKMP_CFG_ATTR_PRIVATE_MAX)
                return 0;
        if (type == 0 || type >= ISAKMP_CFG_ATTR_FUTURE_MIN) {
                LOG_DBG((LOG_NEGOTIATION, 30,
                    "cfg_decode_attribute: invalid attr type %u", type));
                return -1;
        }
        attr = calloc(1, sizeof *attr);
        if (!attr) {
                log_error("cfg_decode_attribute: calloc (1, %lu) failed",
                    (unsigned long)sizeof *attr);
                return -1;
        }
        attr->type = type;
        attr->length = len;
        if (len) {
                attr->value = malloc(len);
                if (!attr->value) {
                        log_error("cfg_decode_attribute: malloc (%d) failed",
                            len);
                        free(attr);
                        /* Should we also deallocate all other values?  */
                        return -1;
                }
                memcpy(attr->value, value, len);
        }
        LIST_INSERT_HEAD(&ie->attrs, attr, link);
        return 0;
}

/*
 * Encode list of attributes from ie->attrs into a attribute payload.
 */
static int
cfg_encode_attributes(struct isakmp_cfg_attr_head *attrs, u_int32_t type,
    u_int32_t cfg_id, char *id_string, u_int8_t **attrp, u_int16_t *len)
{
        struct isakmp_cfg_attr *attr;
        struct sockaddr *sa;
        sa_family_t     family;
        u_int32_t       value;
        u_int16_t       off;
        char           *field;

        /* Compute length */
        *len = ISAKMP_ATTRIBUTE_SZ;
        for (attr = LIST_FIRST(attrs); attr; attr = LIST_NEXT(attr, link)) {
                /* With ACK we only include the attrs we've actually used.  */
                if (type == ISAKMP_CFG_ACK && attr->attr_used == 0)
                        continue;

                switch (attr->type) {
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                case ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY:
                        attr->length = 4;
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET:
                        attr->length = 8;
                        break;

                case ISAKMP_CFG_ATTR_APPLICATION_VERSION:
                        /* XXX So far no version identifier of isakmpd here. */
                        attr->length = 0;
                        break;

                case ISAKMP_CFG_ATTR_SUPPORTED_ATTRIBUTES:
                        attr->length = 2 * 15;
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                        attr->length = 16;
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET:
                        attr->length = 17;
                        break;

                default:
                        attr->ignore++;
                        /* XXX Log!  */
                }
                *len += ISAKMP_ATTR_SZ + attr->length;
        }

        /* Allocate enough space for the payload */
        *attrp = calloc(1, *len);
        if (!*attrp) {
                log_error("cfg_encode_attributes: calloc (1, %lu) failed",
                    (unsigned long)*len);
                return -1;
        }
        SET_ISAKMP_ATTRIBUTE_TYPE(*attrp, type);
        SET_ISAKMP_ATTRIBUTE_ID(*attrp, cfg_id);

        off = ISAKMP_ATTRIBUTE_SZ;
        for (attr = LIST_FIRST(attrs); attr; attr = LIST_NEXT(attr, link)) {
                /* With ACK we only include the attrs we've actually used.  */
                if (type == ISAKMP_CFG_ACK && attr->attr_used == 0)
                        continue;

                switch (attr->type) {
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                        family = AF_INET;
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                        family = AF_INET6;
                        break;

                default:
                        family = 0;
                        break;
                }

                switch (attr->type) {
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                        field = "Address";
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET:
                        field = "Network";      /* XXX or just "Address" */
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                        field = "Netmask";
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                        field = "DHCP-server";
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                        field = "Nameserver";
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                        field = "WINS-server";
                        break;

                default:
                        field = 0;
                }

                switch (attr->type) {
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS:
                case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS:
                        sa = conf_get_address(id_string, field);
                        if (!sa) {
                                LOG_DBG((LOG_NEGOTIATION, 10,
                                    "cfg_responder_send_ATTR: "
                                    "attribute not found: %s", field));
                                attr->length = 0;
                                break;
                        }
                        if (sa->sa_family != family) {
                                log_print("cfg_responder_send_ATTR: "
                                    "attribute %s - expected %s got %s data",
                                    field,
                                    (family == AF_INET ? "IPv4" : "IPv6"),
                                    (sa->sa_family ==
                                        AF_INET ? "IPv4" : "IPv6"));
                                free(sa);
                                attr->length = 0;
                                break;
                        }
                        /* Temporary limit length for the _SUBNET types. */
                        if (attr->type == ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET)
                                attr->length = 4;
                        else if (attr->type ==
                            ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET)
                                attr->length = 16;

                        memcpy(*attrp + off + ISAKMP_ATTR_VALUE_OFF,
                            sockaddr_addrdata(sa), attr->length);
                        free(sa);

                        /* _SUBNET types need some extra work. */
                        if (attr->type ==
                            ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET) {
                                sa = conf_get_address(id_string, "Netmask");
                                if (!sa) {
                                        LOG_DBG((LOG_NEGOTIATION, 10,
                                            "cfg_responder_send_ATTR: "
                                           "attribute not found: Netmask"));
                                        attr->length = 0;
                                        break;
                                }
                                if (sa->sa_family != AF_INET) {
                                        log_print("cfg_responder_send_ATTR: "
                                            "attribute Netmask - expected "
                                            "IPv4 got IPv6 data");
                                        free(sa);
                                        attr->length = 0;
                                        break;
                                }
                                memcpy(*attrp + off + ISAKMP_ATTR_VALUE_OFF +
                                    attr->length, sockaddr_addrdata(sa),
                                    attr->length);
                                attr->length = 8;
                                free(sa);
                        } else if (attr->type ==
                            ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET) {
                                int prefix = conf_get_num(id_string, "Prefix",
                                    -1);

                                if (prefix == -1) {
                                        log_print("cfg_responder_send_ATTR: "
                                            "attribute not found: Prefix");
                                        attr->length = 0;
                                        break;
                                } else if (prefix < -1 || prefix > 128) {
                                        log_print("cfg_responder_send_ATTR: "
                                            "attribute Prefix - invalid "
                                            "value %d", prefix);
                                        attr->length = 0;
                                        break;
                                }
                                *(*attrp + off + ISAKMP_ATTR_VALUE_OFF + 16) =
                                    (u_int8_t)prefix;
                                attr->length = 17;
                        }
                        break;

                case ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY:
                        value = conf_get_num(id_string, "Lifetime", 1200);
                        encode_32(*attrp + off + ISAKMP_ATTR_VALUE_OFF, value);
                        break;

                case ISAKMP_CFG_ATTR_APPLICATION_VERSION:
                        /* XXX So far no version identifier of isakmpd here. */
                        break;

                case ISAKMP_CFG_ATTR_SUPPORTED_ATTRIBUTES:
                        break;

                default:
                        break;
                }

                SET_ISAKMP_ATTR_TYPE(*attrp + off, attr->type);
                SET_ISAKMP_ATTR_LENGTH_VALUE(*attrp + off, attr->length);
                off += ISAKMP_ATTR_VALUE_OFF + attr->length;
        }

        return 0;
}