diff options
author | Othmar Gsenger <otti@anytun.org> | 2007-07-30 19:37:53 +0000 |
---|---|---|
committer | Othmar Gsenger <otti@anytun.org> | 2007-07-30 19:37:53 +0000 |
commit | 6585e5ad764ee2414d9b01f30784b6549bc8f58e (patch) | |
tree | 4ea258d5327838363dc3ac66d09ecc94686f3e26 /keyexchange/isakmpd-20041012/ipsec.c | |
parent | ripe requests, final (diff) |
added keyexchange
Diffstat (limited to 'keyexchange/isakmpd-20041012/ipsec.c')
-rw-r--r-- | keyexchange/isakmpd-20041012/ipsec.c | 2523 |
1 files changed, 2523 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/ipsec.c b/keyexchange/isakmpd-20041012/ipsec.c new file mode 100644 index 0000000..46cb8d9 --- /dev/null +++ b/keyexchange/isakmpd-20041012/ipsec.c @@ -0,0 +1,2523 @@ +/* $OpenBSD: ipsec.c,v 1.104 2004/09/17 13:53:08 ho Exp $ */ +/* $EOM: ipsec.c,v 1.143 2000/12/11 23:57:42 niklas Exp $ */ + +/* + * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. + * Copyright (c) 2001 Angelos D. Keromytis. All rights reserved. + * Copyright (c) 2001 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 <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> + +#include "sysdep.h" + +#include "attribute.h" +#include "conf.h" +#include "constants.h" +#include "crypto.h" +#include "dh.h" +#include "doi.h" +#if defined (USE_DPD) +#include "dpd.h" +#endif +#include "exchange.h" +#include "hash.h" +#include "ike_aggressive.h" +#include "ike_auth.h" +#include "ike_main_mode.h" +#include "ike_quick_mode.h" +#include "ipsec.h" +#include "ipsec_doi.h" +#include "isakmp.h" +#include "isakmp_cfg.h" +#include "isakmp_fld.h" +#include "isakmp_num.h" +#include "log.h" +#include "math_group.h" +#include "message.h" +#if defined (USE_NAT_TRAVERSAL) +#include "nat_traversal.h" +#endif +#include "prf.h" +#include "sa.h" +#include "timer.h" +#include "transport.h" +#include "util.h" +#ifdef USE_X509 +#include "x509.h" +#endif + +extern int acquire_only; + +/* Backwards compatibility. */ +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif + +/* The replay window size used for all IPsec protocols if not overridden. */ +#define DEFAULT_REPLAY_WINDOW 16 + +struct ipsec_decode_arg { + struct message *msg; + struct sa *sa; + struct proto *proto; +}; + +/* These variables hold the contacted peers ADT state. */ +struct contact { + struct sockaddr *addr; + socklen_t len; +} *contacts = 0; +int contact_cnt = 0, contact_limit = 0; + +static int addr_cmp(const void *, const void *); +static int ipsec_add_contact(struct message *); +static int ipsec_contacted(struct message *); +#ifdef USE_DEBUG +static int ipsec_debug_attribute(u_int16_t, u_int8_t *, u_int16_t, + void *); +#endif +static void ipsec_delete_spi(struct sa *, struct proto *, int); +static int16_t *ipsec_exchange_script(u_int8_t); +static void ipsec_finalize_exchange(struct message *); +static void ipsec_free_exchange_data(void *); +static void ipsec_free_proto_data(void *); +static void ipsec_free_sa_data(void *); +static struct keystate *ipsec_get_keystate(struct message *); +static u_int8_t *ipsec_get_spi(size_t *, u_int8_t, struct message *); +static int ipsec_handle_leftover_payload(struct message *, u_int8_t, + struct payload *); +static int ipsec_informational_post_hook(struct message *); +static int ipsec_informational_pre_hook(struct message *); +static int ipsec_initiator(struct message *); +static void ipsec_proto_init(struct proto *, char *); +static int ipsec_responder(struct message *); +static void ipsec_setup_situation(u_int8_t *); +static int ipsec_set_network(u_int8_t *, u_int8_t *, struct ipsec_sa *); +static size_t ipsec_situation_size(void); +static u_int8_t ipsec_spi_size(u_int8_t); +static int ipsec_validate_attribute(u_int16_t, u_int8_t *, u_int16_t, + void *); +static int ipsec_validate_exchange(u_int8_t); +static int ipsec_validate_id_information(u_int8_t, u_int8_t *, u_int8_t *, + size_t, struct exchange *); +static int ipsec_validate_key_information(u_int8_t *, size_t); +static int ipsec_validate_notification(u_int16_t); +static int ipsec_validate_proto(u_int8_t); +static int ipsec_validate_situation(u_int8_t *, size_t *, size_t); +static int ipsec_validate_transform_id(u_int8_t, u_int8_t); + +static struct doi ipsec_doi = { + {0}, IPSEC_DOI_IPSEC, + sizeof(struct ipsec_exch), sizeof(struct ipsec_sa), + sizeof(struct ipsec_proto), +#ifdef USE_DEBUG + ipsec_debug_attribute, +#endif + ipsec_delete_spi, + ipsec_exchange_script, + ipsec_finalize_exchange, + ipsec_free_exchange_data, + ipsec_free_proto_data, + ipsec_free_sa_data, + ipsec_get_keystate, + ipsec_get_spi, + ipsec_handle_leftover_payload, + ipsec_informational_post_hook, + ipsec_informational_pre_hook, + ipsec_is_attribute_incompatible, + ipsec_proto_init, + ipsec_setup_situation, + ipsec_situation_size, + ipsec_spi_size, + ipsec_validate_attribute, + ipsec_validate_exchange, + ipsec_validate_id_information, + ipsec_validate_key_information, + ipsec_validate_notification, + ipsec_validate_proto, + ipsec_validate_situation, + ipsec_validate_transform_id, + ipsec_initiator, + ipsec_responder, + ipsec_decode_ids +}; + +int16_t script_quick_mode[] = { + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + ISAKMP_PAYLOAD_SA, + ISAKMP_PAYLOAD_NONCE, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ + ISAKMP_PAYLOAD_SA, + ISAKMP_PAYLOAD_NONCE, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + EXCHANGE_SCRIPT_END +}; + +int16_t script_new_group_mode[] = { + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + ISAKMP_PAYLOAD_SA, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ + ISAKMP_PAYLOAD_SA, + EXCHANGE_SCRIPT_END +}; + +struct dst_spi_proto_arg { + struct sockaddr *dst; + u_int32_t spi; + u_int8_t proto; +}; + +/* + * Check if SA matches what we are asking for through V_ARG. It has to + * be a finished phase 2 SA. + * if "proto" arg is 0, match any proto + */ +static int +ipsec_sa_check(struct sa *sa, void *v_arg) +{ + struct dst_spi_proto_arg *arg = v_arg; + struct proto *proto; + struct sockaddr *dst, *src; + int incoming; + + if (sa->phase != 2 || !(sa->flags & SA_FLAG_READY)) + return 0; + + sa->transport->vtbl->get_dst(sa->transport, &dst); + if (memcmp(sockaddr_addrdata(dst), sockaddr_addrdata(arg->dst), + sockaddr_addrlen(dst)) == 0) + incoming = 0; + else { + sa->transport->vtbl->get_src(sa->transport, &src); + if (memcmp(sockaddr_addrdata(src), sockaddr_addrdata(arg->dst), + sockaddr_addrlen(src)) == 0) + incoming = 1; + else + return 0; + } + + for (proto = TAILQ_FIRST(&sa->protos); proto; + proto = TAILQ_NEXT(proto, link)) + if ((arg->proto == 0 || proto->proto == arg->proto) && + memcmp(proto->spi[incoming], &arg->spi, sizeof arg->spi) + == 0) + return 1; + return 0; +} + +/* Find an SA with a "name" of DST, SPI & PROTO. */ +struct sa * +ipsec_sa_lookup(struct sockaddr *dst, u_int32_t spi, u_int8_t proto) +{ + struct dst_spi_proto_arg arg; + + arg.dst = dst; + arg.spi = spi; + arg.proto = proto; + return sa_find(ipsec_sa_check, &arg); +} + +/* + * Check if SA matches the flow of another SA in V_ARG. It has to + * be a finished non-replaced phase 2 SA. + * XXX At some point other selectors will matter here too. + */ +static int +ipsec_sa_check_flow(struct sa *sa, void *v_arg) +{ + struct sa *sa2 = v_arg; + struct ipsec_sa *isa = sa->data, *isa2 = sa2->data; + + if (sa == sa2 || sa->phase != 2 || + (sa->flags & (SA_FLAG_READY | SA_FLAG_REPLACED)) != SA_FLAG_READY) + return 0; + + if (isa->tproto != isa2->tproto || isa->sport != isa2->sport || + isa->dport != isa2->dport) + return 0; + + return isa->src_net->sa_family == isa2->src_net->sa_family && + memcmp(sockaddr_addrdata(isa->src_net), + sockaddr_addrdata(isa2->src_net), + sockaddr_addrlen(isa->src_net)) == 0 && + memcmp(sockaddr_addrdata(isa->src_mask), + sockaddr_addrdata(isa2->src_mask), + sockaddr_addrlen(isa->src_mask)) == 0 && + memcmp(sockaddr_addrdata(isa->dst_net), + sockaddr_addrdata(isa2->dst_net), + sockaddr_addrlen(isa->dst_net)) == 0 && + memcmp(sockaddr_addrdata(isa->dst_mask), + sockaddr_addrdata(isa2->dst_mask), + sockaddr_addrlen(isa->dst_mask)) == 0; +} + +/* + * Do IPsec DOI specific finalizations task for the exchange where MSG was + * the final message. + */ +static void +ipsec_finalize_exchange(struct message *msg) +{ + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct sa *sa = 0, *old_sa; + struct proto *proto, *last_proto = 0; +#ifdef USE_DEBUG + char *addr1, *addr2, *mask1, *mask2; +#endif + + switch (exchange->phase) { + case 1: + switch (exchange->type) { + case ISAKMP_EXCH_ID_PROT: + case ISAKMP_EXCH_AGGRESSIVE: + isa = isakmp_sa->data; + isa->hash = ie->hash->type; + isa->prf_type = ie->prf_type; + isa->skeyid_len = ie->skeyid_len; + isa->skeyid_d = ie->skeyid_d; + isa->skeyid_a = ie->skeyid_a; + /* Prevents early free of SKEYID_*. */ + ie->skeyid_a = ie->skeyid_d = 0; + + /* + * If a lifetime was negotiated setup the expiration + * timers. + */ + if (isakmp_sa->seconds) + sa_setup_expirations(isakmp_sa); + +#if defined (USE_NAT_TRAVERSAL) + if (isakmp_sa->flags & SA_FLAG_NAT_T_KEEPALIVE) + nat_t_setup_keepalive(isakmp_sa); +#endif + break; + } + break; + + case 2: + switch (exchange->type) { + case IKE_EXCH_QUICK_MODE: + /* + * Tell the application(s) about the SPIs and key + * material. + */ + for (sa = TAILQ_FIRST(&exchange->sa_list); sa; + sa = TAILQ_NEXT(sa, next)) { + isa = sa->data; + + if (exchange->initiator) { + /* + * Initiator is source, responder is + * destination. + */ + if (ipsec_set_network(ie->id_ci, + ie->id_cr, isa)) { + log_print( + "ipsec_finalize_exchange: " + "ipsec_set_network " + "failed"); + return; + } + } else { + /* + * Responder is source, initiator is + * destination. + */ + if (ipsec_set_network(ie->id_cr, + ie->id_ci, isa)) { + log_print( + "ipsec_finalize_exchange: " + "ipsec_set_network " + "failed"); + return; + } + } + + for (proto = TAILQ_FIRST(&sa->protos), + last_proto = 0; proto; + proto = TAILQ_NEXT(proto, link)) { + if (sysdep_ipsec_set_spi(sa, proto, + 0, isakmp_sa) || + (last_proto && + sysdep_ipsec_group_spis(sa, + last_proto, proto, 0)) || + sysdep_ipsec_set_spi(sa, proto, + 1, isakmp_sa) || + (last_proto && + sysdep_ipsec_group_spis(sa, + last_proto, proto, 1))) + /* + * XXX Tear down this + * exchange. + */ + return; + last_proto = proto; + } + +#ifdef USE_DEBUG + if (sockaddr2text(isa->src_net, &addr1, 0)) + addr1 = 0; + if (sockaddr2text(isa->src_mask, &mask1, 0)) + mask1 = 0; + if (sockaddr2text(isa->dst_net, &addr2, 0)) + addr2 = 0; + if (sockaddr2text(isa->dst_mask, &mask2, 0)) + mask2 = 0; + + LOG_DBG((LOG_EXCHANGE, 50, + "ipsec_finalize_exchange: src %s %s " + "dst %s %s tproto %u sport %u dport %u", + addr1 ? addr1 : "<??\?>", + mask1 ? mask1 : "<??\?>", + addr2 ? addr2 : "<??\?>", + mask2 ? mask2 : "<??\?>", + isa->tproto, ntohs(isa->sport), + ntohs(isa->dport))); + + if (addr1) + free(addr1); + if (mask1) + free(mask1); + if (addr2) + free(addr2); + if (mask2) + free(mask2); + +#endif /* USE_DEBUG */ + + /* + * If this is not an SA acquired by the + * kernel, it needs to have a SPD entry + * (a.k.a. flow) set up. + */ + if (!(sa->flags & SA_FLAG_ONDEMAND || + conf_get_str("General", "Acquire-Only") + || acquire_only) + && sysdep_ipsec_enable_sa(sa, isakmp_sa)) + /* XXX Tear down this exchange. */ + return; + + /* + * Mark elder SAs with the same flow + * information as replaced. + */ + while ((old_sa = sa_find(ipsec_sa_check_flow, + sa)) != 0) + sa_mark_replaced(old_sa); + } + break; + } + } +} + +/* Set the client addresses in ISA from SRC_ID and DST_ID. */ +static int +ipsec_set_network(u_int8_t *src_id, u_int8_t *dst_id, struct ipsec_sa *isa) +{ + int id; + + /* Set source address/mask. */ + id = GET_ISAKMP_ID_TYPE(src_id); + switch (id) { + case IPSEC_ID_IPV4_ADDR: + case IPSEC_ID_IPV4_ADDR_SUBNET: + isa->src_net = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in)); + if (!isa->src_net) + goto memfail; + isa->src_net->sa_family = AF_INET; +#ifndef USE_OLD_SOCKADDR + isa->src_net->sa_len = sizeof(struct sockaddr_in); +#endif + + isa->src_mask = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in)); + if (!isa->src_mask) + goto memfail; + isa->src_mask->sa_family = AF_INET; +#ifndef USE_OLD_SOCKADDR + isa->src_mask->sa_len = sizeof(struct sockaddr_in); +#endif + break; + + case IPSEC_ID_IPV6_ADDR: + case IPSEC_ID_IPV6_ADDR_SUBNET: + isa->src_net = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in6)); + if (!isa->src_net) + goto memfail; + isa->src_net->sa_family = AF_INET6; +#ifndef USE_OLD_SOCKADDR + isa->src_net->sa_len = sizeof(struct sockaddr_in6); +#endif + + isa->src_mask = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in6)); + if (!isa->src_mask) + goto memfail; + isa->src_mask->sa_family = AF_INET6; +#ifndef USE_OLD_SOCKADDR + isa->src_mask->sa_len = sizeof(struct sockaddr_in6); +#endif + break; + + case IPSEC_ID_IPV4_RANGE: + case IPSEC_ID_IPV6_RANGE: + case IPSEC_ID_DER_ASN1_DN: + case IPSEC_ID_DER_ASN1_GN: + case IPSEC_ID_KEY_ID: + default: + log_print("ipsec_set_network: ID type %d (%s) not supported", + id, constant_name(ipsec_id_cst, id)); + return -1; + } + + /* Net */ + memcpy(sockaddr_addrdata(isa->src_net), src_id + ISAKMP_ID_DATA_OFF, + sockaddr_addrlen(isa->src_net)); + + /* Mask */ + switch (id) { + case IPSEC_ID_IPV4_ADDR: + case IPSEC_ID_IPV6_ADDR: + memset(sockaddr_addrdata(isa->src_mask), 0xff, + sockaddr_addrlen(isa->src_mask)); + break; + case IPSEC_ID_IPV4_ADDR_SUBNET: + case IPSEC_ID_IPV6_ADDR_SUBNET: + memcpy(sockaddr_addrdata(isa->src_mask), src_id + + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(isa->src_net), + sockaddr_addrlen(isa->src_mask)); + break; + } + + memcpy(&isa->sport, + src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF, + IPSEC_ID_PORT_LEN); + + /* Set destination address. */ + id = GET_ISAKMP_ID_TYPE(dst_id); + switch (id) { + case IPSEC_ID_IPV4_ADDR: + case IPSEC_ID_IPV4_ADDR_SUBNET: + isa->dst_net = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in)); + if (!isa->dst_net) + goto memfail; + isa->dst_net->sa_family = AF_INET; +#ifndef USE_OLD_SOCKADDR + isa->dst_net->sa_len = sizeof(struct sockaddr_in); +#endif + + isa->dst_mask = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in)); + if (!isa->dst_mask) + goto memfail; + isa->dst_mask->sa_family = AF_INET; +#ifndef USE_OLD_SOCKADDR + isa->dst_mask->sa_len = sizeof(struct sockaddr_in); +#endif + break; + + case IPSEC_ID_IPV6_ADDR: + case IPSEC_ID_IPV6_ADDR_SUBNET: + isa->dst_net = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in6)); + if (!isa->dst_net) + goto memfail; + isa->dst_net->sa_family = AF_INET6; +#ifndef USE_OLD_SOCKADDR + isa->dst_net->sa_len = sizeof(struct sockaddr_in6); +#endif + + isa->dst_mask = (struct sockaddr *)calloc(1, + sizeof(struct sockaddr_in6)); + if (!isa->dst_mask) + goto memfail; + isa->dst_mask->sa_family = AF_INET6; +#ifndef USE_OLD_SOCKADDR + isa->dst_mask->sa_len = sizeof(struct sockaddr_in6); +#endif + break; + } + + /* Net */ + memcpy(sockaddr_addrdata(isa->dst_net), dst_id + ISAKMP_ID_DATA_OFF, + sockaddr_addrlen(isa->dst_net)); + + /* Mask */ + switch (id) { + case IPSEC_ID_IPV4_ADDR: + case IPSEC_ID_IPV6_ADDR: + memset(sockaddr_addrdata(isa->dst_mask), 0xff, + sockaddr_addrlen(isa->dst_mask)); + break; + case IPSEC_ID_IPV4_ADDR_SUBNET: + case IPSEC_ID_IPV6_ADDR_SUBNET: + memcpy(sockaddr_addrdata(isa->dst_mask), dst_id + + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(isa->dst_net), + sockaddr_addrlen(isa->dst_mask)); + break; + } + + memcpy(&isa->tproto, dst_id + ISAKMP_ID_DOI_DATA_OFF + + IPSEC_ID_PROTO_OFF, IPSEC_ID_PROTO_LEN); + memcpy(&isa->dport, + dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF, + IPSEC_ID_PORT_LEN); + return 0; + +memfail: + log_error("ipsec_set_network: calloc () failed"); + return -1; +} + +/* Free the DOI-specific exchange data pointed to by VIE. */ +static void +ipsec_free_exchange_data(void *vie) +{ + struct ipsec_exch *ie = vie; +#ifdef USE_ISAKMP_CFG + struct isakmp_cfg_attr *attr; +#endif + + if (ie->sa_i_b) + free(ie->sa_i_b); + if (ie->id_ci) + free(ie->id_ci); + if (ie->id_cr) + free(ie->id_cr); + if (ie->g_xi) + free(ie->g_xi); + if (ie->g_xr) + free(ie->g_xr); + if (ie->g_xy) + free(ie->g_xy); + if (ie->skeyid) + free(ie->skeyid); + if (ie->skeyid_d) + free(ie->skeyid_d); + if (ie->skeyid_a) + free(ie->skeyid_a); + if (ie->skeyid_e) + free(ie->skeyid_e); + if (ie->hash_i) + free(ie->hash_i); + if (ie->hash_r) + free(ie->hash_r); + if (ie->group) + group_free(ie->group); +#ifdef USE_ISAKMP_CFG + for (attr = LIST_FIRST(&ie->attrs); attr; + attr = LIST_FIRST(&ie->attrs)) { + LIST_REMOVE(attr, link); + if (attr->length) + free(attr->value); + free(attr); + } +#endif +} + +/* Free the DOI-specific SA data pointed to by VISA. */ +static void +ipsec_free_sa_data(void *visa) +{ + struct ipsec_sa *isa = visa; + + if (isa->src_net) + free(isa->src_net); + if (isa->src_mask) + free(isa->src_mask); + if (isa->dst_net) + free(isa->dst_net); + if (isa->dst_mask) + free(isa->dst_mask); + if (isa->skeyid_a) + free(isa->skeyid_a); + if (isa->skeyid_d) + free(isa->skeyid_d); +} + +/* Free the DOI-specific protocol data of an SA pointed to by VIPROTO. */ +static void +ipsec_free_proto_data(void *viproto) +{ + struct ipsec_proto *iproto = viproto; + int i; + + for (i = 0; i < 2; i++) + if (iproto->keymat[i]) + free(iproto->keymat[i]); +} + +/* Return exchange script based on TYPE. */ +static int16_t * +ipsec_exchange_script(u_int8_t type) +{ + switch (type) { +#ifdef USE_ISAKMP_CFG + case ISAKMP_EXCH_TRANSACTION: + return script_transaction; +#endif + case IKE_EXCH_QUICK_MODE: + return script_quick_mode; + case IKE_EXCH_NEW_GROUP_MODE: + return script_new_group_mode; + } + return 0; +} + +/* Initialize this DOI, requires doi_init to already have been called. */ +void +ipsec_init(void) +{ + doi_register(&ipsec_doi); +} + +/* Given a message MSG, return a suitable IV (or rather keystate). */ +static struct keystate * +ipsec_get_keystate(struct message *msg) +{ + struct keystate *ks; + struct hash *hash; + + /* If we have already have an IV, use it. */ + if (msg->exchange && msg->exchange->keystate) { + ks = malloc(sizeof *ks); + if (!ks) { + log_error("ipsec_get_keystate: malloc (%lu) failed", + (unsigned long) sizeof *ks); + return 0; + } + memcpy(ks, msg->exchange->keystate, sizeof *ks); + return ks; + } + /* + * For phase 2 when no SA yet is setup we need to hash the IV used by + * the ISAKMP SA concatenated with the message ID, and use that as an + * IV for further cryptographic operations. + */ + if (!msg->isakmp_sa->keystate) { + log_print("ipsec_get_keystate: no keystate in ISAKMP SA %p", + msg->isakmp_sa); + return 0; + } + ks = crypto_clone_keystate(msg->isakmp_sa->keystate); + if (!ks) + return 0; + + hash = hash_get(((struct ipsec_sa *)msg->isakmp_sa->data)->hash); + hash->Init(hash->ctx); + LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: final phase 1 IV", + ks->riv, ks->xf->blocksize)); + hash->Update(hash->ctx, ks->riv, ks->xf->blocksize); + LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: message ID", + ((u_int8_t *) msg->iov[0].iov_base) + ISAKMP_HDR_MESSAGE_ID_OFF, + ISAKMP_HDR_MESSAGE_ID_LEN)); + hash->Update(hash->ctx, ((u_int8_t *) msg->iov[0].iov_base) + + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN); + hash->Final(hash->digest, hash->ctx); + crypto_init_iv(ks, hash->digest, ks->xf->blocksize); + LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: phase 2 IV", + hash->digest, ks->xf->blocksize)); + return ks; +} + +static void +ipsec_setup_situation(u_int8_t *buf) +{ + SET_IPSEC_SIT_SIT(buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY); +} + +static size_t +ipsec_situation_size(void) +{ + return IPSEC_SIT_SIT_LEN; +} + +static u_int8_t +ipsec_spi_size(u_int8_t proto) +{ + return IPSEC_SPI_SIZE; +} + +static int +ipsec_validate_attribute(u_int16_t type, u_int8_t * value, u_int16_t len, + void *vmsg) +{ + struct message *msg = vmsg; + + if ((msg->exchange->phase == 1 + && (type < IKE_ATTR_ENCRYPTION_ALGORITHM + || type > IKE_ATTR_GROUP_ORDER)) + || (msg->exchange->phase == 2 + && (type < IPSEC_ATTR_SA_LIFE_TYPE + || type > IPSEC_ATTR_ECN_TUNNEL))) + return -1; + return 0; +} + +static int +ipsec_validate_exchange(u_int8_t exch) +{ + return exch != IKE_EXCH_QUICK_MODE && exch != IKE_EXCH_NEW_GROUP_MODE; +} + +static int +ipsec_validate_id_information(u_int8_t type, u_int8_t *extra, u_int8_t *buf, + size_t sz, struct exchange *exchange) +{ + u_int8_t proto = GET_IPSEC_ID_PROTO(extra); + u_int16_t port = GET_IPSEC_ID_PORT(extra); + + LOG_DBG((LOG_MESSAGE, 40, + "ipsec_validate_id_information: proto %d port %d type %d", + proto, port, type)); + if (type < IPSEC_ID_IPV4_ADDR || type > IPSEC_ID_KEY_ID) + return -1; + + switch (type) { + case IPSEC_ID_IPV4_ADDR: + LOG_DBG_BUF((LOG_MESSAGE, 40, + "ipsec_validate_id_information: IPv4", buf, + sizeof(struct in_addr))); + break; + + case IPSEC_ID_IPV6_ADDR: + LOG_DBG_BUF((LOG_MESSAGE, 40, + "ipsec_validate_id_information: IPv6", buf, + sizeof(struct in6_addr))); + break; + + case IPSEC_ID_IPV4_ADDR_SUBNET: + LOG_DBG_BUF((LOG_MESSAGE, 40, + "ipsec_validate_id_information: IPv4 network/netmask", + buf, 2 * sizeof(struct in_addr))); + break; + + case IPSEC_ID_IPV6_ADDR_SUBNET: + LOG_DBG_BUF((LOG_MESSAGE, 40, + "ipsec_validate_id_information: IPv6 network/netmask", + buf, 2 * sizeof(struct in6_addr))); + break; + + default: + break; + } + + if (exchange->phase == 1 + && (proto != IPPROTO_UDP || port != UDP_DEFAULT_PORT) + && (proto != 0 || port != 0)) { + /* + * XXX SSH's ISAKMP tester fails this test (proto 17 - port + * 0). + */ +#ifdef notyet + return -1; +#else + log_print("ipsec_validate_id_information: dubious ID " + "information accepted"); +#endif + } + /* XXX More checks? */ + + return 0; +} + +static int +ipsec_validate_key_information(u_int8_t *buf, size_t sz) +{ + /* XXX Not implemented yet. */ + return 0; +} + +static int +ipsec_validate_notification(u_int16_t type) +{ + return type < IPSEC_NOTIFY_RESPONDER_LIFETIME + || type > IPSEC_NOTIFY_INITIAL_CONTACT ? -1 : 0; +} + +static int +ipsec_validate_proto(u_int8_t proto) +{ + return proto < IPSEC_PROTO_IPSEC_AH + || proto > IPSEC_PROTO_IPCOMP ? -1 : 0; +} + +static int +ipsec_validate_situation(u_int8_t *buf, size_t *sz, size_t len) +{ + if (len < IPSEC_SIT_SIT_OFF + IPSEC_SIT_SIT_LEN) { + log_print("ipsec_validate_situation: payload too short: %u", + (unsigned int) len); + return -1; + } + /* Currently only "identity only" situations are supported. */ + if (GET_IPSEC_SIT_SIT(buf) != IPSEC_SIT_IDENTITY_ONLY) + return 1; + + *sz = IPSEC_SIT_SIT_LEN; + + return 0; +} + +static int +ipsec_validate_transform_id(u_int8_t proto, u_int8_t transform_id) +{ + switch (proto) { + /* + * As no unexpected protocols can occur, we just tie the + * default case to the first case, in orer to silence a GCC + * warning. + */ + default: + case ISAKMP_PROTO_ISAKMP: + return transform_id != IPSEC_TRANSFORM_KEY_IKE; + case IPSEC_PROTO_IPSEC_AH: + return transform_id < IPSEC_AH_MD5 + || transform_id > IPSEC_AH_DES ? -1 : 0; + case IPSEC_PROTO_IPSEC_ESP: + return transform_id < IPSEC_ESP_DES_IV64 + || (transform_id > IPSEC_ESP_AES_128_CTR + && transform_id < IPSEC_ESP_AES_MARS) + || transform_id > IPSEC_ESP_AES_TWOFISH ? -1 : 0; + case IPSEC_PROTO_IPCOMP: + return transform_id < IPSEC_IPCOMP_OUI + || transform_id > IPSEC_IPCOMP_V42BIS ? -1 : 0; + } +} + +static int +ipsec_initiator(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + int (**script)(struct message *) = 0; + + /* Check that the SA is coherent with the IKE rules. */ + if (exchange->type != ISAKMP_EXCH_TRANSACTION + && ((exchange->phase == 1 && + exchange->type != ISAKMP_EXCH_ID_PROT && + exchange->type != ISAKMP_EXCH_AGGRESSIVE && + exchange->type != ISAKMP_EXCH_INFO) + || (exchange->phase == 2 && + exchange->type != IKE_EXCH_QUICK_MODE && + exchange->type != ISAKMP_EXCH_INFO))) { + log_print("ipsec_initiator: unsupported exchange type %d " + "in phase %d", exchange->type, exchange->phase); + return -1; + } + switch (exchange->type) { + case ISAKMP_EXCH_ID_PROT: + script = ike_main_mode_initiator; + break; +#ifdef USE_AGGRESSIVE + case ISAKMP_EXCH_AGGRESSIVE: + script = ike_aggressive_initiator; + break; +#endif +#ifdef USE_ISAKMP_CFG + case ISAKMP_EXCH_TRANSACTION: + script = isakmp_cfg_initiator; + break; +#endif + case ISAKMP_EXCH_INFO: + return message_send_info(msg); + case IKE_EXCH_QUICK_MODE: + script = ike_quick_mode_initiator; + break; + default: + log_print("ipsec_initiator: unsupported exchange type %d", + exchange->type); + return -1; + } + + /* Run the script code for this step. */ + if (script) + return script[exchange->step] (msg); + + return 0; +} + +/* + * delete all SA's from addr with the associated proto and SPI's + * + * spis[] is an array of SPIs of size 16-octet for proto ISAKMP + * or 4-octet otherwise. + */ +static void +ipsec_delete_spi_list(struct sockaddr *addr, u_int8_t proto, u_int8_t *spis, + int nspis, char *type) +{ + struct sa *sa; + int i; + + for (i = 0; i < nspis; i++) { + if (proto == ISAKMP_PROTO_ISAKMP) { + u_int8_t *spi = spis + i * ISAKMP_HDR_COOKIES_LEN; + + /* + * This really shouldn't happen in IPSEC DOI + * code, but Cisco VPN 3000 sends ISAKMP DELETE's + * this way. + */ + sa = sa_lookup_isakmp_sa(addr, spi); + } else { + u_int32_t spi = ((u_int32_t *)spis)[i]; + + sa = ipsec_sa_lookup(addr, spi, proto); + } + + if (sa == NULL) { + LOG_DBG((LOG_SA, 30, "ipsec_delete_spi_list: could " + "not locate SA (SPI %08x, proto %u)", + ((u_int32_t *)spis)[i], proto)); + continue; + } + /* Delete the SA and search for the next */ + LOG_DBG((LOG_SA, 30, "ipsec_delete_spi_list: " + "%s made us delete SA %p (%d references) for proto %d", + type, sa, sa->refcnt, proto)); + + sa_free(sa); + } +} + +/* + * deal with a NOTIFY of INVALID_SPI + */ +static void +ipsec_invalid_spi (struct message *msg, struct payload *p) +{ + struct sockaddr *dst; + int invspisz, off; + u_int32_t spi; + u_int16_t totsiz; + u_int8_t spisz; + + /* Any notification that make us do something should be protected */ + if(!TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH])) + { + LOG_DBG ((LOG_SA, 40, + "ipsec_invalid_spi: missing HASH payload in INVALID_SPI" + " notification")); + return; + } + + /* + * get the invalid spi out of the variable sized notification data + * field, which is after the variable sized SPI field [which specifies + * the receiving entity's phase-1 SPI, not the invalid spi] + */ + totsiz = GET_ISAKMP_GEN_LENGTH (p->p); + spisz = GET_ISAKMP_NOTIFY_SPI_SZ (p->p); + off = ISAKMP_NOTIFY_SPI_OFF + spisz; + invspisz = totsiz - off; + + if (invspisz != sizeof spi) + { + LOG_DBG ((LOG_SA, 40, + "ipsec_invalid_spi: SPI size %d in INVALID_SPI " + "payload unsupported", spisz)); + return; + } + memcpy (&spi, p->p + off, sizeof spi); + + msg->transport->vtbl->get_dst (msg->transport, &dst); + + /* delete matching SPI's from this peer */ + ipsec_delete_spi_list (dst, 0, (u_int8_t *)&spi, 1, "INVALID_SPI"); +} + +static int +ipsec_responder(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + int (**script)(struct message *) = 0; + struct payload *p; + u_int16_t type; + + /* Check that a new exchange is coherent with the IKE rules. */ + if (exchange->step == 0 && exchange->type != ISAKMP_EXCH_TRANSACTION + && ((exchange->phase == 1 && + exchange->type != ISAKMP_EXCH_ID_PROT && + exchange->type != ISAKMP_EXCH_AGGRESSIVE && + exchange->type != ISAKMP_EXCH_INFO) + || (exchange->phase == 2 && + exchange->type != IKE_EXCH_QUICK_MODE && + exchange->type != ISAKMP_EXCH_INFO))) { + message_drop(msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, + 0, 1, 0); + return -1; + } + LOG_DBG((LOG_MISC, 30, "ipsec_responder: phase %d exchange %d step %d", + exchange->phase, exchange->type, exchange->step)); + switch (exchange->type) { + case ISAKMP_EXCH_ID_PROT: + script = ike_main_mode_responder; + break; +#ifdef USE_AGGRESSIVE + case ISAKMP_EXCH_AGGRESSIVE: + script = ike_aggressive_responder; + break; +#endif +#ifdef USE_ISAKMP_CFG + case ISAKMP_EXCH_TRANSACTION: + script = isakmp_cfg_responder; + break; +#endif + case ISAKMP_EXCH_INFO: + for (p = payload_first(msg, ISAKMP_PAYLOAD_NOTIFY); p; + p = TAILQ_NEXT(p, link)) { + type = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p); + LOG_DBG((LOG_EXCHANGE, 10, + "ipsec_responder: got NOTIFY of type %s", + constant_name(isakmp_notify_cst, type))); + + switch (type) { + case IPSEC_NOTIFY_INITIAL_CONTACT: + /* Handled by leftover logic. */ + break; + +#if defined (USE_DPD) + case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE: + case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK: + dpd_handle_notify(msg, p); + break; +#endif + + default: + p->flags |= PL_MARK; + break; + } + } + + /* + * If any DELETEs are in here, let the logic of leftover + * payloads deal with them. + */ + return 0; + + case IKE_EXCH_QUICK_MODE: + script = ike_quick_mode_responder; + break; + + default: + message_drop(msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, + 0, 1, 0); + return -1; + } + + /* Run the script code for this step. */ + if (script) + return script[exchange->step] (msg); + + /* + * XXX So far we don't accept any proposals for exchanges we don't + * support. + */ + if (payload_first(msg, ISAKMP_PAYLOAD_SA)) { + message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + return -1; + } + return 0; +} + +static enum hashes +from_ike_hash(u_int16_t hash) +{ + switch (hash) { + case IKE_HASH_MD5: + return HASH_MD5; + case IKE_HASH_SHA: + return HASH_SHA1; + } + return -1; +} + +static enum transform +from_ike_crypto(u_int16_t crypto) +{ + /* Coincidentally this is the null operation :-) */ + return crypto; +} + +/* + * Find out whether the attribute of type TYPE with a LEN length value + * pointed to by VALUE is incompatible with what we can handle. + * VMSG is a pointer to the current message. + */ +int +ipsec_is_attribute_incompatible(u_int16_t type, u_int8_t *value, u_int16_t len, + void *vmsg) +{ + struct message *msg = vmsg; + u_int16_t dv = decode_16(value); + + if (msg->exchange->phase == 1) { + switch (type) { + case IKE_ATTR_ENCRYPTION_ALGORITHM: + return !crypto_get(from_ike_crypto(dv)); + case IKE_ATTR_HASH_ALGORITHM: + return !hash_get(from_ike_hash(dv)); + case IKE_ATTR_AUTHENTICATION_METHOD: + return !ike_auth_get(dv); + case IKE_ATTR_GROUP_DESCRIPTION: + return (dv < IKE_GROUP_DESC_MODP_768 + || dv > IKE_GROUP_DESC_MODP_1536) + && (dv < IKE_GROUP_DESC_MODP_2048 + || dv > IKE_GROUP_DESC_MODP_8192); + case IKE_ATTR_GROUP_TYPE: + return 1; + case IKE_ATTR_GROUP_PRIME: + return 1; + case IKE_ATTR_GROUP_GENERATOR_1: + return 1; + case IKE_ATTR_GROUP_GENERATOR_2: + return 1; + case IKE_ATTR_GROUP_CURVE_A: + return 1; + case IKE_ATTR_GROUP_CURVE_B: + return 1; + case IKE_ATTR_LIFE_TYPE: + return dv < IKE_DURATION_SECONDS + || dv > IKE_DURATION_KILOBYTES; + case IKE_ATTR_LIFE_DURATION: + return len != 2 && len != 4; + case IKE_ATTR_PRF: + return 1; + case IKE_ATTR_KEY_LENGTH: + /* + * Our crypto routines only allows key-lengths which + * are multiples of an octet. + */ + return dv % 8 != 0; + case IKE_ATTR_FIELD_SIZE: + return 1; + case IKE_ATTR_GROUP_ORDER: + return 1; + } + } else { + switch (type) { + case IPSEC_ATTR_SA_LIFE_TYPE: + return dv < IPSEC_DURATION_SECONDS + || dv > IPSEC_DURATION_KILOBYTES; + case IPSEC_ATTR_SA_LIFE_DURATION: + return len != 2 && len != 4; + case IPSEC_ATTR_GROUP_DESCRIPTION: + return (dv < IKE_GROUP_DESC_MODP_768 + || dv > IKE_GROUP_DESC_MODP_1536) + && (dv < IKE_GROUP_DESC_MODP_2048 + || IKE_GROUP_DESC_MODP_8192 < dv); + case IPSEC_ATTR_ENCAPSULATION_MODE: +#if defined (USE_NAT_TRAVERSAL) + return dv != IPSEC_ENCAP_TUNNEL + && dv != IPSEC_ENCAP_TRANSPORT + && dv != IPSEC_ENCAP_UDP_ENCAP_TUNNEL + && dv != IPSEC_ENCAP_UDP_ENCAP_TRANSPORT + && dv != IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT + && dv != IPSEC_ENCAP_UDP_ENCAP_TRANSPORT_DRAFT; +#else + return dv < IPSEC_ENCAP_TUNNEL + || dv > IPSEC_ENCAP_TRANSPORT; +#endif /* USE_NAT_TRAVERSAL */ + case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: + return dv < IPSEC_AUTH_HMAC_MD5 + || dv > IPSEC_AUTH_HMAC_RIPEMD; + case IPSEC_ATTR_KEY_LENGTH: + /* + * XXX Blowfish needs '0'. Others appear to disregard + * this attr? + */ + return 0; + case IPSEC_ATTR_KEY_ROUNDS: + return 1; + case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: + return 1; + case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: + return 1; + case IPSEC_ATTR_ECN_TUNNEL: + return 1; + } + } + /* XXX Silence gcc. */ + return 1; +} + +#ifdef USE_DEBUG +/* + * Log the attribute of TYPE with a LEN length value pointed to by VALUE + * in human-readable form. VMSG is a pointer to the current message. + */ +int +ipsec_debug_attribute(u_int16_t type, u_int8_t *value, u_int16_t len, + void *vmsg) +{ + struct message *msg = vmsg; + char val[20]; + + /* XXX Transient solution. */ + if (len == 2) + snprintf(val, sizeof val, "%d", decode_16(value)); + else if (len == 4) + snprintf(val, sizeof val, "%d", decode_32(value)); + else + snprintf(val, sizeof val, "unrepresentable"); + + LOG_DBG((LOG_MESSAGE, 50, "Attribute %s value %s", + constant_name(msg->exchange->phase == 1 ? ike_attr_cst : + ipsec_attr_cst, type), val)); + return 0; +} +#endif + +/* + * Decode the attribute of type TYPE with a LEN length value pointed to by + * VALUE. VIDA is a pointer to a context structure where we can find the + * current message, SA and protocol. + */ +int +ipsec_decode_attribute(u_int16_t type, u_int8_t *value, u_int16_t len, + void *vida) +{ + struct ipsec_decode_arg *ida = vida; + struct message *msg = ida->msg; + struct sa *sa = ida->sa; + struct ipsec_sa *isa = sa->data; + struct proto *proto = ida->proto; + struct ipsec_proto *iproto = proto->data; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + static int lifetype = 0; + + if (exchange->phase == 1) { + switch (type) { + case IKE_ATTR_ENCRYPTION_ALGORITHM: + /* XXX Errors possible? */ + exchange->crypto = crypto_get(from_ike_crypto( + decode_16(value))); + break; + case IKE_ATTR_HASH_ALGORITHM: + /* XXX Errors possible? */ + ie->hash = hash_get(from_ike_hash(decode_16(value))); + break; + case IKE_ATTR_AUTHENTICATION_METHOD: + /* XXX Errors possible? */ + ie->ike_auth = ike_auth_get(decode_16(value)); + break; + case IKE_ATTR_GROUP_DESCRIPTION: + isa->group_desc = decode_16(value); + break; + case IKE_ATTR_GROUP_TYPE: + break; + case IKE_ATTR_GROUP_PRIME: + break; + case IKE_ATTR_GROUP_GENERATOR_1: + break; + case IKE_ATTR_GROUP_GENERATOR_2: + break; + case IKE_ATTR_GROUP_CURVE_A: + break; + case IKE_ATTR_GROUP_CURVE_B: + break; + case IKE_ATTR_LIFE_TYPE: + lifetype = decode_16(value); + return 0; + case IKE_ATTR_LIFE_DURATION: + switch (lifetype) { + case IKE_DURATION_SECONDS: + switch (len) { + case 2: + sa->seconds = decode_16(value); + break; + case 4: + sa->seconds = decode_32(value); + break; + default: + log_print("ipsec_decode_attribute: " + "unreasonable lifetime"); + } + break; + case IKE_DURATION_KILOBYTES: + switch (len) { + case 2: + sa->kilobytes = decode_16(value); + break; + case 4: + sa->kilobytes = decode_32(value); + break; + default: + log_print("ipsec_decode_attribute: " + "unreasonable lifetime"); + } + break; + default: + log_print("ipsec_decode_attribute: unknown " + "lifetime type"); + } + break; + case IKE_ATTR_PRF: + break; + case IKE_ATTR_KEY_LENGTH: + exchange->key_length = decode_16(value) / 8; + break; + case IKE_ATTR_FIELD_SIZE: + break; + case IKE_ATTR_GROUP_ORDER: + break; + } + } else { + switch (type) { + case IPSEC_ATTR_SA_LIFE_TYPE: + lifetype = decode_16(value); + return 0; + case IPSEC_ATTR_SA_LIFE_DURATION: + switch (lifetype) { + case IPSEC_DURATION_SECONDS: + switch (len) { + case 2: + sa->seconds = decode_16(value); + break; + case 4: + sa->seconds = decode_32(value); + break; + default: + log_print("ipsec_decode_attribute: " + "unreasonable lifetime"); + } + break; + case IPSEC_DURATION_KILOBYTES: + switch (len) { + case 2: + sa->kilobytes = decode_16(value); + break; + case 4: + sa->kilobytes = decode_32(value); + break; + default: + log_print("ipsec_decode_attribute: " + "unreasonable lifetime"); + } + break; + default: + log_print("ipsec_decode_attribute: unknown " + "lifetime type"); + } + break; + case IPSEC_ATTR_GROUP_DESCRIPTION: + isa->group_desc = decode_16(value); + break; + case IPSEC_ATTR_ENCAPSULATION_MODE: + /* + * XXX Multiple protocols must have same + * encapsulation mode, no? + */ + iproto->encap_mode = decode_16(value); + break; + case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: + iproto->auth = decode_16(value); + break; + case IPSEC_ATTR_KEY_LENGTH: + iproto->keylen = decode_16(value); + break; + case IPSEC_ATTR_KEY_ROUNDS: + iproto->keyrounds = decode_16(value); + break; + case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: + break; + case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: + break; + case IPSEC_ATTR_ECN_TUNNEL: + break; + } + } + lifetype = 0; + return 0; +} + +/* + * Walk over the attributes of the transform payload found in BUF, and + * fill out the fields of the SA attached to MSG. Also mark the SA as + * processed. + */ +void +ipsec_decode_transform(struct message *msg, struct sa *sa, struct proto *proto, + u_int8_t *buf) +{ + struct ipsec_exch *ie = msg->exchange->data; + struct ipsec_decode_arg ida; + + LOG_DBG((LOG_MISC, 20, "ipsec_decode_transform: transform %d chosen", + GET_ISAKMP_TRANSFORM_NO(buf))); + + ida.msg = msg; + ida.sa = sa; + ida.proto = proto; + + /* The default IKE lifetime is 8 hours. */ + if (sa->phase == 1) + sa->seconds = 28800; + + /* Extract the attributes and stuff them into the SA. */ + attribute_map(buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH(buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + ipsec_decode_attribute, &ida); + + /* + * If no pseudo-random function was negotiated, it's HMAC. + * XXX As PRF_HMAC currently is zero, this is a no-op. + */ + if (!ie->prf_type) + ie->prf_type = PRF_HMAC; +} + +/* + * Delete the IPsec SA represented by the INCOMING direction in protocol PROTO + * of the IKE security association SA. + */ +static void +ipsec_delete_spi(struct sa *sa, struct proto *proto, int incoming) +{ + if (sa->phase == 1) + return; + /* XXX Error handling? Is it interesting? */ + sysdep_ipsec_delete_spi(sa, proto, incoming); +} + +/* + * Store BUF into the g^x entry of the exchange that message MSG belongs to. + * PEER is non-zero when the value is our peer's, and zero when it is ours. + */ +static int +ipsec_g_x(struct message *msg, int peer, u_int8_t *buf) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + u_int8_t **g_x; + int initiator = exchange->initiator ^ peer; + char header[32]; + + g_x = initiator ? &ie->g_xi : &ie->g_xr; + *g_x = malloc(ie->g_x_len); + if (!*g_x) { + log_error("ipsec_g_x: malloc (%lu) failed", + (unsigned long)ie->g_x_len); + return -1; + } + memcpy(*g_x, buf, ie->g_x_len); + snprintf(header, sizeof header, "ipsec_g_x: g^x%c", + initiator ? 'i' : 'r'); + LOG_DBG_BUF((LOG_MISC, 80, header, *g_x, ie->g_x_len)); + return 0; +} + +/* Generate our DH value. */ +int +ipsec_gen_g_x(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + u_int8_t *buf; + + buf = malloc(ISAKMP_KE_SZ + ie->g_x_len); + if (!buf) { + log_error("ipsec_gen_g_x: malloc (%lu) failed", + ISAKMP_KE_SZ + (unsigned long)ie->g_x_len); + return -1; + } + if (message_add_payload(msg, ISAKMP_PAYLOAD_KEY_EXCH, buf, + ISAKMP_KE_SZ + ie->g_x_len, 1)) { + free(buf); + return -1; + } + if (dh_create_exchange(ie->group, buf + ISAKMP_KE_DATA_OFF)) { + log_print("ipsec_gen_g_x: dh_create_exchange failed"); + free(buf); + return -1; + } + return ipsec_g_x(msg, 0, buf + ISAKMP_KE_DATA_OFF); +} + +/* Save the peer's DH value. */ +int +ipsec_save_g_x(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct payload *kep; + + kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH); + kep->flags |= PL_MARK; + ie->g_x_len = GET_ISAKMP_GEN_LENGTH(kep->p) - ISAKMP_KE_DATA_OFF; + + /* Check that the given length matches the group's expectancy. */ + if (ie->g_x_len != (size_t) dh_getlen(ie->group)) { + /* XXX Is this a good notify type? */ + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); + return -1; + } + return ipsec_g_x(msg, 1, kep->p + ISAKMP_KE_DATA_OFF); +} + +/* + * Get a SPI for PROTO and the transport MSG passed over. Store the + * size where SZ points. NB! A zero return is OK if *SZ is zero. + */ +static u_int8_t * +ipsec_get_spi(size_t *sz, u_int8_t proto, struct message *msg) +{ + struct sockaddr *dst, *src; + struct transport *transport = msg->transport; + + if (msg->exchange->phase == 1) { + *sz = 0; + return 0; + } else { + /* We are the destination in the SA we want a SPI for. */ + transport->vtbl->get_src(transport, &dst); + /* The peer is the source. */ + transport->vtbl->get_dst(transport, &src); + return sysdep_ipsec_get_spi(sz, proto, src, dst, + msg->exchange->seq); + } +} + +/* + * We have gotten a payload PAYLOAD of type TYPE, which did not get handled + * by the logic of the exchange MSG takes part in. Now is the time to deal + * with such a payload if we know how to, if we don't, return -1, otherwise + * 0. + */ +int +ipsec_handle_leftover_payload(struct message *msg, u_int8_t type, + struct payload *payload) +{ + u_int32_t spisz, nspis; + struct sockaddr *dst; + int reenter = 0; + u_int8_t *spis, proto; + struct sa *sa; + + switch (type) { + case ISAKMP_PAYLOAD_DELETE: + proto = GET_ISAKMP_DELETE_PROTO(payload->p); + nspis = GET_ISAKMP_DELETE_NSPIS(payload->p); + spisz = GET_ISAKMP_DELETE_SPI_SZ(payload->p); + + if (nspis == 0) { + LOG_DBG((LOG_SA, 60, "ipsec_handle_leftover_payload: " + "message specified zero SPIs, ignoring")); + return -1; + } + /* verify proper SPI size */ + if ((proto == ISAKMP_PROTO_ISAKMP && spisz != + ISAKMP_HDR_COOKIES_LEN) + || (proto != ISAKMP_PROTO_ISAKMP && spisz != + sizeof(u_int32_t))) { + log_print("ipsec_handle_leftover_payload: invalid SPI " + "size %d for proto %d in DELETE payload", + spisz, proto); + return -1; + } + spis = (u_int8_t *) malloc(nspis * spisz); + if (!spis) { + log_error("ipsec_handle_leftover_payload: malloc " + "(%d) failed", nspis * spisz); + return -1; + } + /* extract SPI and get dst address */ + memcpy(spis, payload->p + ISAKMP_DELETE_SPI_OFF, nspis * spisz); + msg->transport->vtbl->get_dst(msg->transport, &dst); + + ipsec_delete_spi_list(dst, proto, spis, nspis, "DELETE"); + + free(spis); + payload->flags |= PL_MARK; + return 0; + + case ISAKMP_PAYLOAD_NOTIFY: + switch (GET_ISAKMP_NOTIFY_MSG_TYPE(payload->p)) { + case IPSEC_NOTIFY_INITIAL_CONTACT: + /* + * Permit INITIAL-CONTACT if + * - this is not an AGGRESSIVE mode exchange + * - it is protected by an ISAKMP SA + * + * XXX Instead of the first condition above, we could + * XXX permit this only for phase 2. In the last + * XXX packet of main-mode, this payload, while + * XXX encrypted, is not part of the hash digest. As + * XXX we currently send our own INITIAL-CONTACTs at + * XXX this point, this too would need to be changed. + */ + if (msg->exchange->type == ISAKMP_EXCH_AGGRESSIVE) { + log_print("ipsec_handle_leftover_payload: got " + "INITIAL-CONTACT in AGGRESSIVE mode"); + return -1; + } + if ((msg->exchange->flags & EXCHANGE_FLAG_ENCRYPT) + == 0) { + log_print("ipsec_handle_leftover_payload: got " + "INITIAL-CONTACT without ISAKMP SA"); + return -1; + } + + if ((msg->flags & MSG_AUTHENTICATED) == 0) { + log_print("ipsec_handle_leftover_payload: " + "got unauthenticated INITIAL-CONTACT"); + return -1; + } + /* + * Find out who is sending this and then delete every + * SA that is ready. Exchanges will timeout + * themselves and then the non-ready SAs will + * disappear too. + */ + msg->transport->vtbl->get_dst(msg->transport, &dst); + while ((sa = sa_lookup_by_peer(dst, + sysdep_sa_len(dst))) != 0) { + /* + * Don't delete the current SA -- we received + * the notification over it, so it's obviously + * still active. We temporarily need to remove + * the SA from the list to avoid an endless + * loop, but keep a reference so it won't + * disappear meanwhile. + */ + if (sa == msg->isakmp_sa) { + sa_reference(sa); + sa_remove(sa); + reenter = 1; + continue; + } + LOG_DBG((LOG_SA, 30, + "ipsec_handle_leftover_payload: " + "INITIAL-CONTACT made us delete SA %p", + sa)); + sa_delete(sa, 0); + } + + if (reenter) { + sa_enter(msg->isakmp_sa); + sa_release(msg->isakmp_sa); + } + payload->flags |= PL_MARK; + return 0; + } + } + return -1; +} + +/* Return the encryption keylength in octets of the ESP protocol PROTO. */ +int +ipsec_esp_enckeylength(struct proto *proto) +{ + struct ipsec_proto *iproto = proto->data; + + /* Compute the keylength to use. */ + switch (proto->id) { + case IPSEC_ESP_DES: + case IPSEC_ESP_DES_IV32: + case IPSEC_ESP_DES_IV64: + return 8; + case IPSEC_ESP_3DES: + return 24; + case IPSEC_ESP_CAST: + if (!iproto->keylen) + return 16; + return iproto->keylen / 8; + case IPSEC_ESP_AES: + case IPSEC_ESP_AES_128_CTR: + if (!iproto->keylen) + return 16; + /* Fallthrough */ + default: + return iproto->keylen / 8; + } +} + +/* Return the authentication keylength in octets of the ESP protocol PROTO. */ +int +ipsec_esp_authkeylength(struct proto *proto) +{ + struct ipsec_proto *iproto = proto->data; + + switch (iproto->auth) { + case IPSEC_AUTH_HMAC_MD5: + return 16; + case IPSEC_AUTH_HMAC_SHA: + case IPSEC_AUTH_HMAC_RIPEMD: + return 20; + case IPSEC_AUTH_HMAC_SHA2_256: + return 32; + case IPSEC_AUTH_HMAC_SHA2_384: + return 48; + case IPSEC_AUTH_HMAC_SHA2_512: + return 64; + default: + return 0; + } +} + +/* Return the authentication keylength in octets of the AH protocol PROTO. */ +int +ipsec_ah_keylength(struct proto *proto) +{ + switch (proto->id) { + case IPSEC_AH_MD5: + return 16; + case IPSEC_AH_SHA: + case IPSEC_AH_RIPEMD: + return 20; + case IPSEC_AH_SHA2_256: + return 32; + case IPSEC_AH_SHA2_384: + return 48; + case IPSEC_AH_SHA2_512: + return 64; + default: + return -1; + } +} + +/* Return the total keymaterial length of the protocol PROTO. */ +int +ipsec_keymat_length(struct proto *proto) +{ + switch (proto->proto) { + case IPSEC_PROTO_IPSEC_ESP: + return ipsec_esp_enckeylength(proto) + + ipsec_esp_authkeylength(proto); + case IPSEC_PROTO_IPSEC_AH: + return ipsec_ah_keylength(proto); + default: + return -1; + } +} + +/* Helper function for ipsec_get_id(). */ +static int +ipsec_get_proto_port(char *section, u_int8_t *tproto, u_int16_t *port) +{ + struct protoent *pe = NULL; + struct servent *se; + char *pstr; + + pstr = conf_get_str(section, "Protocol"); + if (!pstr) { + *tproto = 0; + return 0; + } + *tproto = (u_int8_t)atoi(pstr); + if (!*tproto) { + pe = getprotobyname(pstr); + if (pe) + *tproto = pe->p_proto; + } + if (!*tproto) { + log_print("ipsec_get_proto_port: protocol \"%s\" unknown", + pstr); + return -1; + } + + pstr = conf_get_str(section, "Port"); + if (!pstr) + return 0; + *port = (u_int16_t)atoi(pstr); + if (!*port) { + se = getservbyname(pstr, + pe ? pe->p_name : (pstr ? pstr : NULL)); + if (se) + *port = se->s_port; + } + if (!*port) { + log_print("ipsec_get_proto_port: port \"%s\" unknown", + pstr); + return -1; + } + return 0; +} + +/* + * Out of a named section SECTION in the configuration file find out + * the network address and mask as well as the ID type. Put the info + * in the areas pointed to by ADDR, MASK, TPROTO, PORT, and ID respectively. + * Return 0 on success and -1 on failure. + */ +int +ipsec_get_id(char *section, int *id, struct sockaddr **addr, + struct sockaddr **mask, u_int8_t *tproto, u_int16_t *port) +{ + char *type, *address, *netmask; + + type = conf_get_str(section, "ID-type"); + if (!type) { + log_print("ipsec_get_id: section %s has no \"ID-type\" tag", + section); + return -1; + } + *id = constant_value(ipsec_id_cst, type); + switch (*id) { + case IPSEC_ID_IPV4_ADDR: + case IPSEC_ID_IPV6_ADDR: { + int ret; + + address = conf_get_str(section, "Address"); + if (!address) { + log_print("ipsec_get_id: section %s has no " + "\"Address\" tag", section); + return -1; + } + if (text2sockaddr(address, NULL, addr)) { + log_print("ipsec_get_id: invalid address %s in " + "section %s", address, section); + return -1; + } + ret = ipsec_get_proto_port(section, tproto, port); + if (ret < 0) + free(*addr); + + return ret; + } + +#ifdef notyet + case IPSEC_ID_FQDN: + return -1; + + case IPSEC_ID_USER_FQDN: + return -1; +#endif + + case IPSEC_ID_IPV4_ADDR_SUBNET: + case IPSEC_ID_IPV6_ADDR_SUBNET: { + int ret; + + address = conf_get_str(section, "Network"); + if (!address) { + log_print("ipsec_get_id: section %s has no " + "\"Network\" tag", section); + return -1; + } + if (text2sockaddr(address, NULL, addr)) { + log_print("ipsec_get_id: invalid section %s " + "network %s", section, address); + return -1; + } + netmask = conf_get_str(section, "Netmask"); + if (!netmask) { + log_print("ipsec_get_id: section %s has no " + "\"Netmask\" tag", section); + free(*addr); + return -1; + } + if (text2sockaddr(netmask, NULL, mask)) { + log_print("ipsec_id_build: invalid section %s " + "network %s", section, netmask); + free(*addr); + return -1; + } + ret = ipsec_get_proto_port(section, tproto, port); + if (ret < 0) { + free(*mask); + free(*addr); + } + return ret; + } + +#ifdef notyet + case IPSEC_ID_IPV4_RANGE: + return -1; + + case IPSEC_ID_IPV6_RANGE: + return -1; + + case IPSEC_ID_DER_ASN1_DN: + return -1; + + case IPSEC_ID_DER_ASN1_GN: + return -1; + + case IPSEC_ID_KEY_ID: + return -1; +#endif + + default: + log_print("ipsec_get_id: unknown ID type \"%s\" in " + "section %s", type, section); + return -1; + } + + return 0; +} + +/* + * XXX I rather want this function to return a status code, and fail if + * we cannot fit the information in the supplied buffer. + */ +static void +ipsec_decode_id(char *buf, size_t size, u_int8_t *id, size_t id_len, + int isakmpform) +{ + int id_type; + char *addr = 0, *mask = 0; + u_int32_t *idp; + + if (id) { + if (!isakmpform) { + /* + * Exchanges and SAs dont carry the IDs in ISAKMP + * form. + */ + id -= ISAKMP_GEN_SZ; + id_len += ISAKMP_GEN_SZ; + } + id_type = GET_ISAKMP_ID_TYPE(id); + idp = (u_int32_t *) (id + ISAKMP_ID_DATA_OFF); + switch (id_type) { + case IPSEC_ID_IPV4_ADDR: + util_ntoa(&addr, AF_INET, id + ISAKMP_ID_DATA_OFF); + snprintf(buf, size, "%08x: %s", + decode_32(id + ISAKMP_ID_DATA_OFF), addr); + break; + + case IPSEC_ID_IPV4_ADDR_SUBNET: + util_ntoa(&addr, AF_INET, id + ISAKMP_ID_DATA_OFF); + util_ntoa(&mask, AF_INET, id + ISAKMP_ID_DATA_OFF + 4); + snprintf(buf, size, "%08x/%08x: %s/%s", + decode_32(id + ISAKMP_ID_DATA_OFF), + decode_32(id + ISAKMP_ID_DATA_OFF + 4), addr, mask); + break; + + case IPSEC_ID_IPV6_ADDR: + util_ntoa(&addr, AF_INET6, id + ISAKMP_ID_DATA_OFF); + snprintf(buf, size, "%08x%08x%08x%08x: %s", *idp, + *(idp + 1), *(idp + 2), *(idp + 3), addr); + break; + + case IPSEC_ID_IPV6_ADDR_SUBNET: + util_ntoa(&addr, AF_INET6, id + ISAKMP_ID_DATA_OFF); + util_ntoa(&mask, AF_INET6, id + ISAKMP_ID_DATA_OFF + + sizeof(struct in6_addr)); + snprintf(buf, size, + "%08x%08x%08x%08x/%08x%08x%08x%08x: %s/%s", *idp, + *(idp + 1), *(idp + 2), *(idp + 3), *(idp + 4), + *(idp + 5), *(idp + 6), *(idp + 7), addr, mask); + break; + + case IPSEC_ID_FQDN: + case IPSEC_ID_USER_FQDN: + /* String is not NUL terminated, be careful */ + id_len -= ISAKMP_ID_DATA_OFF; + id_len = MIN(id_len, size - 1); + memcpy(buf, id + ISAKMP_ID_DATA_OFF, id_len); + buf[id_len] = '\0'; + break; + +#ifdef USE_X509 + case IPSEC_ID_DER_ASN1_DN: + addr = x509_DN_string(id + ISAKMP_ID_DATA_OFF, + id_len - ISAKMP_ID_DATA_OFF); + if (!addr) { + snprintf(buf, size, "unparsable ASN1 DN ID"); + return; + } + strlcpy(buf, addr, size); + break; +#endif + + default: + snprintf(buf, size, "<id type unknown: %x>", id_type); + break; + } + } else + snprintf(buf, size, "<no ipsec id>"); + if (addr) + free(addr); + if (mask) + free(mask); +} + +char * +ipsec_decode_ids(char *fmt, u_int8_t *id1, size_t id1_len, u_int8_t *id2, + size_t id2_len, int isakmpform) +{ + static char result[1024]; + char s_id1[256], s_id2[256]; + + ipsec_decode_id(s_id1, sizeof s_id1, id1, id1_len, isakmpform); + ipsec_decode_id(s_id2, sizeof s_id2, id2, id2_len, isakmpform); + + snprintf(result, sizeof result, fmt, s_id1, s_id2); + return result; +} + +/* + * Out of a named section SECTION in the configuration file build an + * ISAKMP ID payload. Ths payload size should be stashed in SZ. + * The caller is responsible for freeing the payload. + */ +u_int8_t * +ipsec_build_id(char *section, size_t *sz) +{ + struct sockaddr *addr, *mask; + u_int8_t *p; + int id, subnet = 0; + u_int8_t tproto = 0; + u_int16_t port = 0; + + if (ipsec_get_id(section, &id, &addr, &mask, &tproto, &port)) + return 0; + + if (id == IPSEC_ID_IPV4_ADDR_SUBNET || id == IPSEC_ID_IPV6_ADDR_SUBNET) + subnet = 1; + + *sz = ISAKMP_ID_SZ + sockaddr_addrlen(addr); + if (subnet) + *sz += sockaddr_addrlen(mask); + + p = malloc(*sz); + if (!p) { + log_print("ipsec_build_id: malloc(%lu) failed", + (unsigned long)*sz); + if (subnet) + free(mask); + free(addr); + return 0; + } + SET_ISAKMP_ID_TYPE(p, id); + SET_ISAKMP_ID_DOI_DATA(p, (unsigned char *)"\000\000\000"); + + memcpy(p + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(addr), + sockaddr_addrlen(addr)); + if (subnet) + memcpy(p + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(addr), + sockaddr_addrdata(mask), sockaddr_addrlen(mask)); + + SET_IPSEC_ID_PROTO(p + ISAKMP_ID_DOI_DATA_OFF, tproto); + SET_IPSEC_ID_PORT(p + ISAKMP_ID_DOI_DATA_OFF, port); + + if (subnet) + free(mask); + free(addr); + return p; +} + +/* + * copy an ISAKMPD id + */ +int +ipsec_clone_id(u_int8_t **did, size_t *did_len, u_int8_t *id, size_t id_len) +{ + if (*did) + free(*did); + + if (!id_len || !id) { + *did = 0; + *did_len = 0; + return 0; + } + *did = malloc(id_len); + if (!*did) { + *did_len = 0; + log_error("ipsec_clone_id: malloc(%lu) failed", + (unsigned long)id_len); + return -1; + } + *did_len = id_len; + memcpy(*did, id, id_len); + + return 0; +} + +/* + * IPsec-specific PROTO initializations. SECTION is only set if we are the + * initiator thus only usable there. + * XXX I want to fix this later. + */ +void +ipsec_proto_init(struct proto *proto, char *section) +{ + struct ipsec_proto *iproto = proto->data; + + if (proto->sa->phase == 2) + iproto->replay_window = section ? conf_get_num(section, + "ReplayWindow", DEFAULT_REPLAY_WINDOW) : + DEFAULT_REPLAY_WINDOW; +} + +/* + * Add a notification payload of type INITIAL CONTACT to MSG if this is + * the first contact we have made to our peer. + */ +int +ipsec_initial_contact(struct message *msg) +{ + u_int8_t *buf; + + if (ipsec_contacted(msg)) + return 0; + + buf = malloc(ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN); + if (!buf) { + log_error("ike_phase_1_initial_contact: malloc (%d) failed", + ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN); + return -1; + } + SET_ISAKMP_NOTIFY_DOI(buf, IPSEC_DOI_IPSEC); + SET_ISAKMP_NOTIFY_PROTO(buf, ISAKMP_PROTO_ISAKMP); + SET_ISAKMP_NOTIFY_SPI_SZ(buf, ISAKMP_HDR_COOKIES_LEN); + SET_ISAKMP_NOTIFY_MSG_TYPE(buf, IPSEC_NOTIFY_INITIAL_CONTACT); + memcpy(buf + ISAKMP_NOTIFY_SPI_OFF, msg->isakmp_sa->cookies, + ISAKMP_HDR_COOKIES_LEN); + if (message_add_payload(msg, ISAKMP_PAYLOAD_NOTIFY, buf, + ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN, 1)) { + free(buf); + return -1; + } + return ipsec_add_contact(msg); +} + +/* + * Compare the two contacts pointed to by A and B. Return negative if + * *A < *B, 0 if they are equal, and positive if *A is the largest of them. + */ +static int +addr_cmp(const void *a, const void *b) +{ + const struct contact *x = a, *y = b; + int minlen = MIN(x->len, y->len); + int rv = memcmp(x->addr, y->addr, minlen); + + return rv ? rv : (x->len - y->len); +} + +/* + * Add the peer that MSG is bound to as an address we don't want to send + * INITIAL CONTACT too from now on. Do not call this function with a + * specific address duplicate times. We want fast lookup, speed of insertion + * is unimportant, if this is to scale. + */ +static int +ipsec_add_contact(struct message *msg) +{ + struct contact *new_contacts; + struct sockaddr *dst, *addr; + int cnt; + + if (contact_cnt == contact_limit) { + cnt = contact_limit ? 2 * contact_limit : 64; + new_contacts = realloc(contacts, cnt * sizeof contacts[0]); + if (!new_contacts) { + log_error("ipsec_add_contact: " + "realloc (%p, %lu) failed", contacts, + cnt * (unsigned long) sizeof contacts[0]); + return -1; + } + contact_limit = cnt; + contacts = new_contacts; + } + msg->transport->vtbl->get_dst(msg->transport, &dst); + addr = malloc(sysdep_sa_len(dst)); + if (!addr) { + log_error("ipsec_add_contact: malloc (%d) failed", + sysdep_sa_len(dst)); + return -1; + } + memcpy(addr, dst, sysdep_sa_len(dst)); + contacts[contact_cnt].addr = addr; + contacts[contact_cnt++].len = sysdep_sa_len(dst); + + /* + * XXX There are better algorithms for already mostly-sorted data like + * this, but only qsort is standard. I will someday do this inline. + */ + qsort(contacts, contact_cnt, sizeof *contacts, addr_cmp); + return 0; +} + +/* Return true if the recipient of MSG has already been contacted. */ +static int +ipsec_contacted(struct message *msg) +{ + struct contact contact; + + msg->transport->vtbl->get_dst(msg->transport, &contact.addr); + contact.len = sysdep_sa_len(contact.addr); + return contacts ? (bsearch(&contact, contacts, contact_cnt, + sizeof *contacts, addr_cmp) != 0) : 0; +} + +/* Add a HASH for to MSG. */ +u_int8_t * +ipsec_add_hash_payload(struct message *msg, size_t hashsize) +{ + u_int8_t *buf; + + buf = malloc(ISAKMP_HASH_SZ + hashsize); + if (!buf) { + log_error("ipsec_add_hash_payload: malloc (%lu) failed", + ISAKMP_HASH_SZ + (unsigned long) hashsize); + return 0; + } + if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) { + free(buf); + return 0; + } + return buf; +} + +/* Fill in the HASH payload of MSG. */ +int +ipsec_fill_in_hash(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct hash *hash = hash_get(isa->hash); + struct prf *prf; + struct payload *payload; + u_int8_t *buf; + u_int32_t i; + char header[80]; + + /* If no SKEYID_a, we need not do anything. */ + if (!isa->skeyid_a) + return 0; + + payload = payload_first(msg, ISAKMP_PAYLOAD_HASH); + if (!payload) { + log_print("ipsec_fill_in_hash: no HASH payload found"); + return -1; + } + buf = payload->p; + + /* Allocate the prf and start calculating our HASH(1). */ + LOG_DBG_BUF((LOG_MISC, 90, "ipsec_fill_in_hash: 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_MISC, 90, "ipsec_fill_in_hash: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN)); + prf->Update(prf->prfctx, exchange->message_id, + ISAKMP_HDR_MESSAGE_ID_LEN); + + /* Loop over all payloads after HASH(1). */ + for (i = 2; i < msg->iovlen; i++) { + /* XXX Misleading payload type printouts. */ + snprintf(header, sizeof header, + "ipsec_fill_in_hash: payload %d after HASH(1)", i - 1); + LOG_DBG_BUF((LOG_MISC, 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); + LOG_DBG_BUF((LOG_MISC, 80, "ipsec_fill_in_hash: HASH(1)", buf + + ISAKMP_HASH_DATA_OFF, hash->hashsize)); + + return 0; +} + +/* Add a HASH payload to MSG, if we have an ISAKMP SA we're protected by. */ +static int +ipsec_informational_pre_hook(struct message *msg) +{ + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa; + struct hash *hash; + + if (!isakmp_sa) + return 0; + isa = isakmp_sa->data; + hash = hash_get(isa->hash); + return ipsec_add_hash_payload(msg, hash->hashsize) == 0; +} + +/* + * Fill in the HASH payload in MSG, if we have an ISAKMP SA we're protected by. + */ +static int +ipsec_informational_post_hook(struct message *msg) +{ + if (!msg->isakmp_sa) + return 0; + return ipsec_fill_in_hash(msg); +} + +ssize_t +ipsec_id_size(char *section, u_int8_t *id) +{ + char *type, *data; + + type = conf_get_str(section, "ID-type"); + if (!type) { + log_print("ipsec_id_size: section %s has no \"ID-type\" tag", + section); + return -1; + } + *id = constant_value(ipsec_id_cst, type); + switch (*id) { + case IPSEC_ID_IPV4_ADDR: + return sizeof(struct in_addr); + case IPSEC_ID_IPV4_ADDR_SUBNET: + return 2 * sizeof(struct in_addr); + case IPSEC_ID_IPV6_ADDR: + return sizeof(struct in6_addr); + case IPSEC_ID_IPV6_ADDR_SUBNET: + return 2 * sizeof(struct in6_addr); + case IPSEC_ID_FQDN: + case IPSEC_ID_USER_FQDN: + case IPSEC_ID_KEY_ID: + case IPSEC_ID_DER_ASN1_DN: + case IPSEC_ID_DER_ASN1_GN: + data = conf_get_str(section, "Name"); + if (!data) { + log_print("ipsec_id_size: " + "section %s has no \"Name\" tag", section); + return -1; + } + return strlen(data); + } + log_print("ipsec_id_size: unrecognized/unsupported ID-type %d (%s)", + *id, type); + return -1; +} + +/* + * Generate a string version of the ID. + */ +char * +ipsec_id_string(u_int8_t *id, size_t id_len) +{ + char *buf = 0; + char *addrstr = 0; + size_t len, size; + + /* + * XXX Real ugly way of making the offsets correct. Be aware that id + * now will point before the actual buffer and cannot be dereferenced + * without an offset larger than or equal to ISAKM_GEN_SZ. + */ + id -= ISAKMP_GEN_SZ; + + /* This is the actual length of the ID data field. */ + id_len += ISAKMP_GEN_SZ - ISAKMP_ID_DATA_OFF; + + /* + * Conservative allocation. + * XXX I think the ASN1 DN case can be thought through to give a better + * estimate. + */ + size = MAX(sizeof "ipv6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + sizeof "asn1_dn/" + id_len - ISAKMP_ID_DATA_OFF); + buf = malloc(size); + if (!buf) + /* XXX Log? */ + goto fail; + + switch (GET_ISAKMP_ID_TYPE(id)) { + case IPSEC_ID_IPV4_ADDR: + if (id_len < sizeof(struct in_addr)) + goto fail; + util_ntoa(&addrstr, AF_INET, id + ISAKMP_ID_DATA_OFF); + if (!addrstr) + goto fail; + snprintf(buf, size, "ipv4/%s", addrstr); + break; + + case IPSEC_ID_IPV6_ADDR: + if (id_len < sizeof(struct in6_addr)) + goto fail; + util_ntoa(&addrstr, AF_INET6, id + ISAKMP_ID_DATA_OFF); + if (!addrstr) + goto fail; + snprintf(buf, size, "ipv6/%s", addrstr); + break; + + case IPSEC_ID_FQDN: + case IPSEC_ID_USER_FQDN: + strlcpy(buf, + GET_ISAKMP_ID_TYPE(id) == IPSEC_ID_FQDN ? "fqdn/" : "ufqdn/", + size); + len = strlen(buf); + + memcpy(buf + len, id + ISAKMP_ID_DATA_OFF, id_len); + *(buf + len + id_len) = '\0'; + break; + +#ifdef USE_X509 + case IPSEC_ID_DER_ASN1_DN: + strlcpy(buf, "asn1_dn/", size); + len = strlen(buf); + addrstr = x509_DN_string(id + ISAKMP_ID_DATA_OFF, + id_len - ISAKMP_ID_DATA_OFF); + if (!addrstr) + goto fail; + if (size < len + strlen(addrstr) + 1) + goto fail; + strlcpy(buf + len, addrstr, size - len); + break; +#endif + + default: + /* Unknown type. */ + LOG_DBG((LOG_MISC, 10, + "ipsec_id_string: unknown identity type %d\n", + GET_ISAKMP_ID_TYPE(id))); + goto fail; + } + + if (addrstr) + free(addrstr); + return buf; + +fail: + if (buf) + free(buf); + if (addrstr) + free(addrstr); + return 0; +} |