summaryrefslogtreecommitdiff
path: root/keyexchange/isakmpd-20041012/isakmp_cfg.c
diff options
context:
space:
mode:
Diffstat (limited to 'keyexchange/isakmpd-20041012/isakmp_cfg.c')
-rw-r--r--keyexchange/isakmpd-20041012/isakmp_cfg.c982
1 files changed, 982 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/isakmp_cfg.c b/keyexchange/isakmpd-20041012/isakmp_cfg.c
new file mode 100644
index 0000000..222d0c6
--- /dev/null
+++ b/keyexchange/isakmpd-20041012/isakmp_cfg.c
@@ -0,0 +1,982 @@
+/* $OpenBSD: isakmp_cfg.c,v 1.34 2004/08/08 19:11:06 deraadt 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 "sysdep.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);
+ getrandom((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:
+ if (id_string)
+ 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;
+}