summaryrefslogtreecommitdiff
path: root/keyexchange/isakmpd-20041012/exchange.c
diff options
context:
space:
mode:
Diffstat (limited to 'keyexchange/isakmpd-20041012/exchange.c')
-rw-r--r--keyexchange/isakmpd-20041012/exchange.c1799
1 files changed, 1799 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/exchange.c b/keyexchange/isakmpd-20041012/exchange.c
new file mode 100644
index 0000000..1c4ef1f
--- /dev/null
+++ b/keyexchange/isakmpd-20041012/exchange.c
@@ -0,0 +1,1799 @@
+/* $OpenBSD: exchange.c,v 1.104 2004/09/17 13:53:08 ho Exp $ */
+/* $EOM: exchange.c,v 1.143 2000/12/04 00:02:25 angelos Exp $ */
+
+/*
+ * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
+ * Copyright (c) 1999, 2001 Angelos D. Keromytis. All rights reserved.
+ * Copyright (c) 1999, 2000, 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 Ericsson Radio Systems.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sysdep.h"
+
+#include "cert.h"
+#include "conf.h"
+#include "connection.h"
+#include "constants.h"
+#include "cookie.h"
+#include "crypto.h"
+#include "doi.h"
+#include "exchange.h"
+#include "ipsec_num.h"
+#include "isakmp.h"
+#ifdef USE_ISAKMP_CFG
+#include "isakmp_cfg.h"
+#endif
+#include "libcrypto.h"
+#include "log.h"
+#include "message.h"
+#include "timer.h"
+#include "transport.h"
+#include "ipsec.h"
+#include "sa.h"
+#include "util.h"
+#include "key.h"
+
+/* Initial number of bits from the cookies used as hash. */
+#define INITIAL_BUCKET_BITS 6
+
+/*
+ * Don't try to use more bits than this as a hash.
+ * We only XOR 16 bits so going above that means changing the code below
+ * too.
+ */
+#define MAX_BUCKET_BITS 16
+
+#ifdef USE_DEBUG
+static void exchange_dump(char *, struct exchange *);
+#endif
+static void exchange_free_aux(void *);
+#if 0
+static void exchange_resize(void);
+#endif
+static struct exchange *exchange_lookup_active(char *, int);
+
+static
+LIST_HEAD(exchange_list, exchange) *exchange_tab;
+
+/* Works both as a maximum index and a mask. */
+static int bucket_mask;
+
+/*
+ * Validation scripts used to test messages for correct content of
+ * payloads depending on the exchange type.
+ */
+int16_t script_base[] = {
+ ISAKMP_PAYLOAD_SA, /* Initiator -> responder. */
+ ISAKMP_PAYLOAD_NONCE,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_SA, /* Responder -> initiator. */
+ ISAKMP_PAYLOAD_NONCE,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_KEY_EXCH, /* Initiator -> responder. */
+ ISAKMP_PAYLOAD_ID,
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_KEY_EXCH, /* Responder -> initiator. */
+ ISAKMP_PAYLOAD_ID,
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_END
+};
+
+int16_t script_identity_protection[] = {
+ ISAKMP_PAYLOAD_SA, /* Initiator -> responder. */
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_SA, /* Responder -> initiator. */
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_KEY_EXCH, /* Initiator -> responder. */
+ ISAKMP_PAYLOAD_NONCE,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_KEY_EXCH, /* Responder -> initiator. */
+ ISAKMP_PAYLOAD_NONCE,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_ID, /* Initiator -> responder. */
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_ID, /* Responder -> initiator. */
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_END
+};
+
+int16_t script_authentication_only[] = {
+ ISAKMP_PAYLOAD_SA, /* Initiator -> responder. */
+ ISAKMP_PAYLOAD_NONCE,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_SA, /* Responder -> initiator. */
+ ISAKMP_PAYLOAD_NONCE,
+ ISAKMP_PAYLOAD_ID,
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_ID, /* Initiator -> responder. */
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_END
+};
+
+#ifdef USE_AGGRESSIVE
+int16_t script_aggressive[] = {
+ ISAKMP_PAYLOAD_SA, /* Initiator -> responder. */
+ ISAKMP_PAYLOAD_KEY_EXCH,
+ ISAKMP_PAYLOAD_NONCE,
+ ISAKMP_PAYLOAD_ID,
+ EXCHANGE_SCRIPT_SWITCH,
+ ISAKMP_PAYLOAD_SA, /* Responder -> initiator. */
+ ISAKMP_PAYLOAD_KEY_EXCH,
+ ISAKMP_PAYLOAD_NONCE,
+ ISAKMP_PAYLOAD_ID,
+ EXCHANGE_SCRIPT_AUTH,
+ EXCHANGE_SCRIPT_SWITCH,
+ EXCHANGE_SCRIPT_AUTH, /* Initiator -> responder. */
+ EXCHANGE_SCRIPT_END
+};
+#endif /* USE_AGGRESSIVE */
+
+int16_t script_informational[] = {
+ EXCHANGE_SCRIPT_INFO, /* Initiator -> responder. */
+ EXCHANGE_SCRIPT_END
+};
+
+/*
+ * Check what exchange SA is negotiated with and return a suitable validation
+ * script.
+ */
+int16_t *
+exchange_script(struct exchange *exchange)
+{
+ switch (exchange->type) {
+ case ISAKMP_EXCH_BASE:
+ return script_base;
+ case ISAKMP_EXCH_ID_PROT:
+ return script_identity_protection;
+ case ISAKMP_EXCH_AUTH_ONLY:
+ return script_authentication_only;
+#ifdef USE_AGGRESSIVE
+ case ISAKMP_EXCH_AGGRESSIVE:
+ return script_aggressive;
+#endif
+ case ISAKMP_EXCH_INFO:
+ return script_informational;
+#ifdef USE_ISAKMP_CFG
+ case ISAKMP_EXCH_TRANSACTION:
+ return script_transaction;
+#endif
+ default:
+ if (exchange->type >= ISAKMP_EXCH_DOI_MIN &&
+ exchange->type <= ISAKMP_EXCH_DOI_MAX)
+ return exchange->doi->exchange_script(exchange->type);
+ }
+ return 0;
+}
+
+/*
+ * Validate the message MSG's contents wrt what payloads the exchange type
+ * requires at this point in the dialogoue. Return -1 if the validation fails,
+ * 0 if it succeeds and the script is not finished and 1 if it's ready.
+ */
+static int
+exchange_validate(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+ int16_t *pc = exchange->exch_pc;
+
+ while (*pc != EXCHANGE_SCRIPT_END && *pc != EXCHANGE_SCRIPT_SWITCH) {
+ LOG_DBG((LOG_EXCHANGE, 90,
+ "exchange_validate: checking for required %s",
+ *pc >= ISAKMP_PAYLOAD_NONE
+ ? constant_name(isakmp_payload_cst, *pc)
+ : constant_name(exchange_script_cst, *pc)));
+
+ /* Check for existence of the required payloads. */
+ if ((*pc > 0 && !payload_first(msg, *pc))
+ || (*pc == EXCHANGE_SCRIPT_AUTH
+ && !payload_first(msg, ISAKMP_PAYLOAD_HASH)
+ && !payload_first(msg, ISAKMP_PAYLOAD_SIG))
+ || (*pc == EXCHANGE_SCRIPT_INFO
+ && ((!payload_first(msg, ISAKMP_PAYLOAD_NOTIFY)
+ && !payload_first(msg, ISAKMP_PAYLOAD_DELETE))
+ || (payload_first(msg, ISAKMP_PAYLOAD_DELETE)
+ && !payload_first(msg, ISAKMP_PAYLOAD_HASH))))) {
+ /* Missing payload. */
+ LOG_DBG((LOG_MESSAGE, 70,
+ "exchange_validate: msg %p requires missing %s",
+ msg, *pc >= ISAKMP_PAYLOAD_NONE
+ ? constant_name(isakmp_payload_cst, *pc)
+ : constant_name(exchange_script_cst, *pc)));
+ return -1;
+ }
+ pc++;
+ }
+ if (*pc == EXCHANGE_SCRIPT_END)
+ /* Cleanup. */
+ return 1;
+
+ return 0;
+}
+
+/* Feed unhandled payloads to the DOI for handling. Help for exchange_run(). */
+static void
+exchange_handle_leftover_payloads(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+ struct doi *doi = exchange->doi;
+ struct payload *p;
+ int i;
+
+ for (i = ISAKMP_PAYLOAD_SA; i < payload_index_max; i++) {
+ if (i == ISAKMP_PAYLOAD_PROPOSAL ||
+ i == ISAKMP_PAYLOAD_TRANSFORM)
+ continue;
+ for (p = payload_first(msg, i); p;
+ p = TAILQ_NEXT(p, link)) {
+ if (p->flags & PL_MARK)
+ continue;
+ if (!doi->handle_leftover_payload ||
+ doi->handle_leftover_payload(msg, i, p))
+ LOG_DBG((LOG_EXCHANGE, 10,
+ "exchange_run: unexpected payload %s",
+ constant_name(isakmp_payload_cst, i)));
+ }
+ }
+}
+
+/*
+ * Run the exchange script from a point given by the "program counter"
+ * upto either the script's end or a transmittal of a message. If we are
+ * at the point of a reception of a message, that message should be handed
+ * in here in the MSG argument. Otherwise we are the initiator and should
+ * expect MSG to be a half-cooked message without payloads.
+ */
+void
+exchange_run(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+ struct doi *doi = exchange->doi;
+ int (*handler)(struct message *) = exchange->initiator ?
+ doi->initiator : doi->responder;
+ int done = 0;
+
+ while (!done) {
+ /*
+ * It's our turn if we're either the initiator on an even step,
+ * or the responder on an odd step of the dialogue.
+ */
+ if (exchange->initiator ^ (exchange->step % 2)) {
+ done = 1;
+ if (exchange->step)
+ msg = message_alloc_reply(msg);
+ message_setup_header(msg, exchange->type, 0,
+ exchange->message_id);
+ if (handler(msg)) {
+ /*
+ * This can happen when transient starvation
+ * of memory occurs.
+ * XXX The peer's retransmit ought to
+ * kick-start this exchange again. If he's
+ * stopped retransmitting he's likely dropped
+ * the SA at his side so we need to do that
+ * too, i.e. implement automatic SA teardown
+ * after a certain amount of inactivity.
+ */
+ log_print("exchange_run: doi->%s (%p) failed",
+ exchange->initiator ? "initiator" :
+ "responder", msg);
+ message_free(msg);
+ return;
+ }
+ switch (exchange_validate(msg)) {
+ case 1:
+ /*
+ * The last message of a multi-message
+ * exchange should not be retransmitted other
+ * than "on-demand", i.e. if we see
+ * retransmits of the last message of the peer
+ * later.
+ */
+ msg->flags |= MSG_LAST;
+ if (exchange->step > 0) {
+ if (exchange->last_sent)
+ message_free(exchange->last_sent);
+ exchange->last_sent = msg;
+ }
+ /*
+ * After we physically have sent our last
+ * message we need to do SA-specific
+ * finalization, like telling our application
+ * the SA is ready to be used, or issuing a
+ * CONNECTED notify if we set the COMMIT bit.
+ */
+ message_register_post_send(msg,
+ exchange_finalize);
+
+ /* Fallthrough. */
+
+ case 0:
+ /* XXX error handling. */
+ message_send(msg);
+ break;
+
+ default:
+ log_print("exchange_run: exchange_validate "
+ "failed, DOI error");
+ exchange_free(exchange);
+ message_free(msg);
+ return;
+ }
+ } else {
+ done = exchange_validate(msg);
+ switch (done) {
+ case 0:
+ case 1:
+ /* Feed the message to the DOI. */
+ if (handler(msg)) {
+ /*
+ * Trust the peer to retransmit.
+ * XXX We have to implement SA aging
+ * with automatic teardown.
+ */
+ message_free(msg);
+ return;
+ }
+ /*
+ * Go over the yet unhandled payloads and feed
+ * them to DOI for handling.
+ */
+ exchange_handle_leftover_payloads(msg);
+
+ /*
+ * We have advanced the state. If we have
+ * been processing an incoming message, record
+ * that message as the one to do duplication
+ * tests against.
+ */
+ if (exchange->last_received)
+ message_free(exchange->last_received);
+ exchange->last_received = msg;
+ if (exchange->flags & EXCHANGE_FLAG_ENCRYPT)
+ crypto_update_iv(exchange->keystate);
+
+ if (done) {
+ exchange_finalize(msg);
+ return;
+ }
+ break;
+
+ case -1:
+ log_print("exchange_run: exchange_validate "
+ "failed");
+ /*
+ * XXX Is this the best error notification
+ * type?
+ */
+ message_drop(msg,
+ ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
+ return;
+ }
+ }
+
+ LOG_DBG((LOG_EXCHANGE, 40,
+ "exchange_run: exchange %p finished step %d, advancing...",
+ exchange, exchange->step));
+ exchange->step++;
+ while (*exchange->exch_pc != EXCHANGE_SCRIPT_SWITCH
+ && *exchange->exch_pc != EXCHANGE_SCRIPT_END)
+ exchange->exch_pc++;
+ exchange->exch_pc++;
+ }
+}
+
+void
+exchange_init(void)
+{
+ int i;
+
+ bucket_mask = (1 << INITIAL_BUCKET_BITS) - 1;
+ exchange_tab = malloc((bucket_mask + 1) *
+ sizeof(struct exchange_list));
+ if (!exchange_tab)
+ log_fatal("exchange_init: out of memory");
+ for (i = 0; i <= bucket_mask; i++)
+ LIST_INIT(&exchange_tab[i]);
+}
+
+#if 0
+/* XXX Currently unused. */
+static void
+exchange_resize(void)
+{
+ struct exchange_list *new_tab;
+ int new_mask = (bucket_mask + 1) * 2 - 1;
+ int i;
+
+ new_tab = realloc(exchange_tab,
+ (new_mask + 1) * sizeof(struct exchange_list));
+ if (!new_tab)
+ return;
+ for (i = bucket_mask + 1; i <= new_mask; i++)
+ LIST_INIT(&new_tab[i]);
+ bucket_mask = new_mask;
+ /* XXX Rehash existing entries. */
+}
+#endif
+
+/* Lookup a phase 1 exchange out of just the initiator cookie. */
+struct exchange *
+exchange_lookup_from_icookie(u_int8_t * cookie)
+{
+ struct exchange *exchange;
+ int i;
+
+ for (i = 0; i <= bucket_mask; i++)
+ for (exchange = LIST_FIRST(&exchange_tab[i]); exchange;
+ exchange = LIST_NEXT(exchange, link))
+ if (memcmp(exchange->cookies, cookie,
+ ISAKMP_HDR_ICOOKIE_LEN) == 0 &&
+ exchange->phase == 1)
+ return exchange;
+ return 0;
+}
+
+/* Lookup an exchange out of the name and phase. */
+struct exchange *
+exchange_lookup_by_name(char *name, int phase)
+{
+ struct exchange *exchange;
+ int i;
+
+ /* If we search for nothing, we will find nothing. */
+ if (!name)
+ return 0;
+
+ for (i = 0; i <= bucket_mask; i++)
+ for (exchange = LIST_FIRST(&exchange_tab[i]); exchange;
+ exchange = LIST_NEXT(exchange, link)) {
+ LOG_DBG((LOG_EXCHANGE, 90,
+ "exchange_lookup_by_name: %s == %s && %d == %d?",
+ name, exchange->name ? exchange->name :
+ "<unnamed>", phase, exchange->phase));
+
+ /*
+ * Match by name, but don't select finished exchanges,
+ * i.e where MSG_LAST are set in last_sent msg.
+ */
+ if (exchange->name &&
+ strcasecmp(exchange->name, name) == 0 &&
+ exchange->phase == phase &&
+ (!exchange->last_sent ||
+ (exchange->last_sent->flags & MSG_LAST) == 0))
+ return exchange;
+ }
+ return 0;
+}
+
+/* Lookup an exchange out of the name, phase and step > 1. */
+static struct exchange *
+exchange_lookup_active(char *name, int phase)
+{
+ struct exchange *exchange;
+ int i;
+
+ /* XXX Almost identical to exchange_lookup_by_name. */
+
+ if (!name)
+ return 0;
+
+ for (i = 0; i <= bucket_mask; i++)
+ for (exchange = LIST_FIRST(&exchange_tab[i]); exchange;
+ exchange = LIST_NEXT(exchange, link)) {
+ LOG_DBG((LOG_EXCHANGE, 90,
+ "exchange_lookup_active: %s == %s && %d == %d?",
+ name, exchange->name ? exchange->name :
+ "<unnamed>", phase, exchange->phase));
+ if (exchange->name &&
+ strcasecmp(exchange->name, name) == 0 &&
+ exchange->phase == phase) {
+ if (exchange->step > 1)
+ return exchange;
+ else
+ LOG_DBG((LOG_EXCHANGE, 80,
+ "exchange_lookup_active: avoided "
+ "early (pre-step 1) exchange %p",
+ exchange));
+ }
+ }
+ return 0;
+}
+
+static void
+exchange_enter(struct exchange *exchange)
+{
+ u_int16_t bucket = 0;
+ u_int8_t *cp;
+ int i;
+
+ /* XXX We might resize if we are crossing a certain threshold */
+
+ for (i = 0; i < ISAKMP_HDR_COOKIES_LEN; i += 2) {
+ cp = exchange->cookies + i;
+ /* Doing it this way avoids alignment problems. */
+ bucket ^= cp[0] | cp[1] << 8;
+ }
+ for (i = 0; i < ISAKMP_HDR_MESSAGE_ID_LEN; i += 2) {
+ cp = exchange->message_id + i;
+ /* Doing it this way avoids alignment problems. */
+ bucket ^= cp[0] | cp[1] << 8;
+ }
+ bucket &= bucket_mask;
+ LIST_INSERT_HEAD(&exchange_tab[bucket], exchange, link);
+}
+
+/*
+ * Lookup the exchange given by the header fields MSG. PHASE2 is false when
+ * looking for phase 1 exchanges and true otherwise.
+ */
+struct exchange *
+exchange_lookup(u_int8_t *msg, int phase2)
+{
+ struct exchange *exchange;
+ u_int16_t bucket = 0;
+ u_int8_t *cp;
+ int i;
+
+ /*
+ * We use the cookies to get bits to use as an index into exchange_tab,
+ * as at least one (our cookie) is a good hash, xoring all the bits,
+ * 16 at a time, and then masking, should do. Doing it this way means
+ * we can validate cookies very fast thus delimiting the effects of
+ * "Denial of service"-attacks using packet flooding.
+ */
+ for (i = 0; i < ISAKMP_HDR_COOKIES_LEN; i += 2) {
+ cp = msg + ISAKMP_HDR_COOKIES_OFF + i;
+ /* Doing it this way avoids alignment problems. */
+ bucket ^= cp[0] | cp[1] << 8;
+ }
+ if (phase2)
+ for (i = 0; i < ISAKMP_HDR_MESSAGE_ID_LEN; i += 2) {
+ cp = msg + ISAKMP_HDR_MESSAGE_ID_OFF + i;
+ /* Doing it this way avoids alignment problems. */
+ bucket ^= cp[0] | cp[1] << 8;
+ }
+ bucket &= bucket_mask;
+ for (exchange = LIST_FIRST(&exchange_tab[bucket]);
+ exchange && (memcmp(msg + ISAKMP_HDR_COOKIES_OFF,
+ exchange->cookies, ISAKMP_HDR_COOKIES_LEN) != 0 ||
+ (phase2 && memcmp(msg + ISAKMP_HDR_MESSAGE_ID_OFF,
+ exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN) != 0) ||
+ (!phase2 && !zero_test(msg + ISAKMP_HDR_MESSAGE_ID_OFF,
+ ISAKMP_HDR_MESSAGE_ID_LEN)));
+ exchange = LIST_NEXT(exchange, link))
+ ;
+
+ return exchange;
+}
+
+/*
+ * Create a phase PHASE exchange where INITIATOR denotes our role. DOI
+ * is the domain of interpretation identifier and TYPE tells what exchange
+ * type to use per either the DOI document or the ISAKMP spec proper.
+ * NSA tells how many SAs we should pre-allocate, and should be zero
+ * when we have the responder role.
+ */
+static struct exchange *
+exchange_create(int phase, int initiator, int doi, int type)
+{
+ struct exchange *exchange;
+ struct timeval expiration;
+ int delta;
+
+ /*
+ * We want the exchange zeroed for exchange_free to be able to find
+ * out what fields have been filled-in.
+ */
+ exchange = calloc(1, sizeof *exchange);
+ if (!exchange) {
+ log_error("exchange_create: calloc (1, %lu) failed",
+ (unsigned long)sizeof *exchange);
+ return 0;
+ }
+ exchange->phase = phase;
+ exchange->step = 0;
+ exchange->initiator = initiator;
+ memset(exchange->cookies, 0, ISAKMP_HDR_COOKIES_LEN);
+ memset(exchange->message_id, 0, ISAKMP_HDR_MESSAGE_ID_LEN);
+ exchange->doi = doi_lookup(doi);
+ exchange->type = type;
+ exchange->policy_id = -1;
+ exchange->exch_pc = exchange_script(exchange);
+ exchange->last_sent = exchange->last_received = 0;
+ TAILQ_INIT(&exchange->sa_list);
+ TAILQ_INIT(&exchange->aca_list);
+
+ /* Allocate the DOI-specific structure and initialize it to zeroes. */
+ if (exchange->doi->exchange_size) {
+ exchange->data = calloc(1, exchange->doi->exchange_size);
+ if (!exchange->data) {
+ log_error("exchange_create: calloc (1, %lu) failed",
+ (unsigned long)exchange->doi->exchange_size);
+ exchange_free(exchange);
+ return 0;
+ }
+ }
+ gettimeofday(&expiration, 0);
+ delta = conf_get_num("General", "Exchange-max-time",
+ EXCHANGE_MAX_TIME);
+ expiration.tv_sec += delta;
+ exchange->death = timer_add_event("exchange_free_aux",
+ exchange_free_aux, exchange, &expiration);
+ if (!exchange->death) {
+ /* If we don't give up we might start leaking... */
+ exchange_free_aux(exchange);
+ return 0;
+ }
+ return exchange;
+}
+
+struct exchange_finalization_node {
+ void (*first)(struct exchange *, void *, int);
+ void *first_arg;
+ void (*second)(struct exchange *, void *, int);
+ void *second_arg;
+};
+
+/* Run the finalization functions of ARG. */
+static void
+exchange_run_finalizations(struct exchange *exchange, void *arg, int fail)
+{
+ struct exchange_finalization_node *node = arg;
+
+ node->first(exchange, node->first_arg, fail);
+ node->second(exchange, node->second_arg, fail);
+ free(node);
+}
+
+/*
+ * Add a finalization function FINALIZE with argument ARG to the tail
+ * of the finalization function list of EXCHANGE.
+ */
+static void
+exchange_add_finalization(struct exchange *exchange,
+ void (*finalize)(struct exchange *, void *, int), void *arg)
+{
+ struct exchange_finalization_node *node;
+
+ if (!finalize)
+ return;
+
+ if (!exchange->finalize) {
+ exchange->finalize = finalize;
+ exchange->finalize_arg = arg;
+ return;
+ }
+ node = malloc(sizeof *node);
+ if (!node) {
+ log_error("exchange_add_finalization: malloc (%lu) failed",
+ (unsigned long)sizeof *node);
+ free(arg);
+ return;
+ }
+ node->first = exchange->finalize;
+ node->first_arg = exchange->finalize_arg;
+ node->second = finalize;
+ node->second_arg = arg;
+ exchange->finalize = exchange_run_finalizations;
+ exchange->finalize_arg = node;
+}
+
+#ifdef USE_ISAKMP_CFG
+static void
+exchange_establish_transaction(struct exchange *exchange, void *arg, int fail)
+{
+ /* Establish a TRANSACTION exchange. */
+ struct exchange_finalization_node *node =
+ (struct exchange_finalization_node *)arg;
+ struct sa *isakmp_sa = sa_lookup_by_name((char *) node->second_arg, 1);
+
+ if (isakmp_sa && !fail)
+ exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_TRANSACTION, 0, 0,
+ node->first, node->first_arg);
+
+ free(node);
+}
+#endif /* USE_ISAKMP_CFG */
+
+/* Establish a phase 1 exchange. */
+void
+exchange_establish_p1(struct transport *t, u_int8_t type, u_int32_t doi,
+ char *name, void *args, void (*finalize)(struct exchange *, void *, int),
+ void *arg)
+{
+ struct exchange *exchange;
+ struct message *msg;
+#ifdef USE_ISAKMP_CFG
+ struct conf_list *flags;
+ struct conf_list_node *flag;
+#endif
+ char *tag = 0;
+ char *str;
+
+ if (name) {
+ /* If no exchange type given, fetch from the configuration. */
+ if (type == 0) {
+ /*
+ * XXX Similar code can be found in
+ * exchange_setup_p1. Share?
+ */
+
+ /* Find out our phase 1 mode. */
+ tag = conf_get_str(name, "Configuration");
+ if (!tag) {
+ /* Use default setting. */
+ tag = CONF_DFLT_TAG_PHASE1_CONFIG;
+ }
+ /* Figure out the DOI. XXX Factor out? */
+ str = conf_get_str(tag, "DOI");
+ if (!str || strcasecmp(str, "IPSEC") == 0)
+ doi = IPSEC_DOI_IPSEC;
+ else if (strcasecmp(str, "ISAKMP") == 0)
+ doi = ISAKMP_DOI_ISAKMP;
+ else {
+ log_print("exchange_establish_p1: "
+ "DOI \"%s\" unsupported", str);
+ return;
+ }
+
+ /* What exchange type do we want? */
+ str = conf_get_str(tag, "EXCHANGE_TYPE");
+ if (!str) {
+ log_print("exchange_establish_p1: "
+ "no \"EXCHANGE_TYPE\" tag in [%s] section",
+ tag);
+ return;
+ }
+ type = constant_value(isakmp_exch_cst, str);
+ if (!type) {
+ log_print("exchange_setup_p1: "
+ "unknown exchange type %s", str);
+ return;
+ }
+ }
+ }
+ exchange = exchange_create(1, 1, doi, type);
+ if (!exchange) {
+ /* XXX Do something here? */
+ return;
+ }
+ if (name) {
+ exchange->name = strdup(name);
+ if (!exchange->name) {
+ log_error("exchange_establish_p1: "
+ "strdup (\"%s\") failed", name);
+ exchange_free(exchange);
+ return;
+ }
+ }
+ exchange->policy = name ? conf_get_str(name, "Configuration") : 0;
+ if (!exchange->policy && name)
+ exchange->policy = CONF_DFLT_TAG_PHASE1_CONFIG;
+
+#ifdef USE_ISAKMP_CFG
+ if (name && (flags = conf_get_list(name, "Flags")) != NULL) {
+ for (flag = TAILQ_FIRST(&flags->fields); flag;
+ flag = TAILQ_NEXT(flag, link))
+ if (strcasecmp(flag->field, "ikecfg") == 0) {
+ struct exchange_finalization_node *node;
+
+ node = calloc(1, (unsigned long)sizeof *node);
+ if (!node) {
+ log_print("exchange_establish_p1: "
+ "calloc (1, %lu) failed",
+ (unsigned long)sizeof(*node));
+ exchange_free(exchange);
+ return;
+ }
+ /*
+ * Insert this finalization inbetween
+ * the original.
+ */
+ node->first = finalize;
+ node->first_arg = arg;
+ node->second_arg = name;
+ exchange_add_finalization(exchange,
+ exchange_establish_transaction,
+ node);
+ finalize = 0;
+ }
+ conf_free_list(flags);
+ }
+#endif /* USE_ISAKMP_CFG */
+
+ exchange_add_finalization(exchange, finalize, arg);
+ cookie_gen(t, exchange, exchange->cookies, ISAKMP_HDR_ICOOKIE_LEN);
+ exchange_enter(exchange);
+#ifdef USE_DEBUG
+ exchange_dump("exchange_establish_p1", exchange);
+#endif
+
+ msg = message_alloc(t, 0, ISAKMP_HDR_SZ);
+ if (!msg) {
+ log_print("exchange_establish_p1: message_alloc () failed");
+ exchange_free(exchange);
+ return;
+ }
+ msg->exchange = exchange;
+
+ /* Do not create SA for an information or transaction exchange. */
+ if (exchange->type != ISAKMP_EXCH_INFO
+ && exchange->type != ISAKMP_EXCH_TRANSACTION) {
+ /*
+ * Don't install a transport into this SA as it will be an
+ * INADDR_ANY address in the local end, which is not good at
+ * all. Let the reply packet install the transport instead.
+ */
+ sa_create(exchange, 0);
+ msg->isakmp_sa = TAILQ_FIRST(&exchange->sa_list);
+ if (!msg->isakmp_sa) {
+ /* XXX Do something more here? */
+ message_free(msg);
+ exchange_free(exchange);
+ return;
+ }
+ sa_reference(msg->isakmp_sa);
+ }
+ msg->extra = args;
+
+ exchange_run(msg);
+}
+
+/* Establish a phase 2 exchange. XXX With just one SA for now. */
+void
+exchange_establish_p2(struct sa *isakmp_sa, u_int8_t type, char *name,
+ void *args, void (*finalize)(struct exchange *, void *, int), void *arg)
+{
+ struct exchange *exchange;
+ struct message *msg;
+ u_int32_t doi = ISAKMP_DOI_ISAKMP;
+ u_int32_t seq = 0;
+ int i;
+ char *tag, *str;
+
+ if (isakmp_sa)
+ doi = isakmp_sa->doi->id;
+
+ if (name) {
+ /* Find out our phase 2 modes. */
+ tag = conf_get_str(name, "Configuration");
+ if (!tag) {
+ log_print("exchange_establish_p2: "
+ "no configuration for peer \"%s\"", name);
+ return;
+ }
+ seq = (u_int32_t)conf_get_num(name, "Acquire-ID", 0);
+
+ /* Figure out the DOI. */
+ str = conf_get_str(tag, "DOI");
+ if (!str || strcasecmp(str, "IPSEC") == 0)
+ doi = IPSEC_DOI_IPSEC;
+ else if (strcasecmp(str, "ISAKMP") == 0)
+ doi = ISAKMP_DOI_ISAKMP;
+ else {
+ log_print("exchange_establish_p2: "
+ "DOI \"%s\" unsupported", str);
+ return;
+ }
+
+ /* What exchange type do we want? */
+ if (!type) {
+ str = conf_get_str(tag, "EXCHANGE_TYPE");
+ if (!str) {
+ log_print("exchange_establish_p2: "
+ "no \"EXCHANGE_TYPE\" tag in [%s] section",
+ tag);
+ return;
+ }
+ /* XXX IKE dependent. */
+ type = constant_value(ike_exch_cst, str);
+ if (!type) {
+ log_print("exchange_establish_p2: unknown "
+ "exchange type %s", str);
+ return;
+ }
+ }
+ }
+ exchange = exchange_create(2, 1, doi, type);
+ if (!exchange) {
+ /* XXX Do something here? */
+ return;
+ }
+ if (name) {
+ exchange->name = strdup(name);
+ if (!exchange->name) {
+ log_error("exchange_establish_p2: "
+ "strdup (\"%s\") failed", name);
+ exchange_free(exchange);
+ return;
+ }
+ }
+ exchange->policy = name ? conf_get_str(name, "Configuration") : 0;
+ exchange->finalize = finalize;
+ exchange->finalize_arg = arg;
+ exchange->seq = seq;
+ memcpy(exchange->cookies, isakmp_sa->cookies, ISAKMP_HDR_COOKIES_LEN);
+ getrandom(exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
+ exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
+#if defined (USE_NAT_TRAVERSAL)
+ if (isakmp_sa->flags & SA_FLAG_NAT_T_ENABLE)
+ exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE;
+ if (isakmp_sa->flags & SA_FLAG_NAT_T_KEEPALIVE)
+ exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE;
+#endif
+ exchange_enter(exchange);
+#ifdef USE_DEBUG
+ exchange_dump("exchange_establish_p2", exchange);
+#endif
+
+ /*
+ * Do not create SA's for informational exchanges.
+ * XXX How to handle new group mode?
+ */
+ if (exchange->type != ISAKMP_EXCH_INFO &&
+ exchange->type != ISAKMP_EXCH_TRANSACTION) {
+ /* XXX Number of SAs should come from the args structure. */
+ for (i = 0; i < 1; i++)
+ if (sa_create(exchange, isakmp_sa->transport)) {
+ exchange_free(exchange);
+ return;
+ }
+ }
+ msg = message_alloc(isakmp_sa->transport, 0, ISAKMP_HDR_SZ);
+ msg->isakmp_sa = isakmp_sa;
+ sa_reference(isakmp_sa);
+
+ msg->extra = args;
+
+ /* This needs to be done late or else get_keystate won't work right. */
+ msg->exchange = exchange;
+
+ exchange_run(msg);
+}
+
+/* Out of an incoming phase 1 message, setup an exchange. */
+struct exchange *
+exchange_setup_p1(struct message *msg, u_int32_t doi)
+{
+ struct transport *t = msg->transport;
+ struct exchange *exchange;
+ struct sockaddr *dst;
+#ifdef USE_ISAKMP_CFG
+ struct conf_list *flags;
+ struct conf_list_node *flag;
+#endif
+ char *name = 0, *policy = 0, *str;
+ u_int32_t want_doi;
+ u_int8_t type;
+
+ /* XXX Similar code can be found in exchange_establish_p1. Share? */
+
+ /*
+ * Unless this is an informational exchange, look up our policy for
+ * this peer.
+ */
+ type = GET_ISAKMP_HDR_EXCH_TYPE(msg->iov[0].iov_base);
+ if (type != ISAKMP_EXCH_INFO) {
+ /*
+ * Find out our inbound phase 1 mode.
+ */
+ t->vtbl->get_dst(t, &dst);
+ if (sockaddr2text(dst, &str, 0) == -1)
+ return 0;
+ name = conf_get_str("Phase 1", str);
+ free(str);
+ if (name) {
+ /*
+ * If another phase 1 exchange is ongoing don't bother
+ * returning the call. However, we will need to
+ * continue responding if our phase 1 exchange is
+ * still waiting for step 1 (i.e still half-open).
+ */
+ if (exchange_lookup_active(name, 1))
+ return 0;
+ } else {
+ name = conf_get_str("Phase 1", "Default");
+ if (!name) {
+ log_print("exchange_setup_p1: no \"Default\" "
+ "tag in [Phase 1] section");
+ return 0;
+ }
+ }
+
+ policy = conf_get_str(name, "Configuration");
+ if (!policy)
+ policy = CONF_DFLT_TAG_PHASE1_CONFIG;
+
+ /* Figure out the DOI. */
+ str = conf_get_str(policy, "DOI");
+ if (!str || strcasecmp(str, "IPSEC") == 0)
+ want_doi = IPSEC_DOI_IPSEC;
+ else if (strcasecmp(str, "ISAKMP") == 0)
+ want_doi = ISAKMP_DOI_ISAKMP;
+ else {
+ log_print("exchange_setup_p1: "
+ "DOI \"%s\" unsupported", str);
+ return 0;
+ }
+ if (want_doi != doi) {
+ /* XXX Should I tell what DOI I got? */
+ log_print("exchange_setup_p1: expected %s DOI", str);
+ return 0;
+ }
+ /* What exchange type do we want? */
+ str = conf_get_str(policy, "EXCHANGE_TYPE");
+ if (!str) {
+ log_print("exchange_setup_p1: no \"EXCHANGE_TYPE\" "
+ "tag in [%s] section", policy);
+ return 0;
+ }
+ type = constant_value(isakmp_exch_cst, str);
+ if (!type) {
+ log_print("exchange_setup_p1: "
+ "unknown exchange type %s", str);
+ return 0;
+ }
+ if (type != GET_ISAKMP_HDR_EXCH_TYPE(msg->iov[0].iov_base)) {
+ log_print("exchange_setup_p1: "
+ "expected exchange type %s got %s", str,
+ constant_name(isakmp_exch_cst,
+ GET_ISAKMP_HDR_EXCH_TYPE(msg->iov[0].iov_base)));
+ return 0;
+ }
+ }
+ exchange = exchange_create(1, 0, doi, type);
+ if (!exchange)
+ return 0;
+
+ exchange->name = name ? strdup(name) : 0;
+ if (name && !exchange->name) {
+ log_error("exchange_setup_p1: strdup (\"%s\") failed", name);
+ exchange_free(exchange);
+ return 0;
+ }
+ exchange->policy = policy;
+
+#ifdef USE_ISAKMP_CFG
+ if (name && (flags = conf_get_list(name, "Flags")) != NULL) {
+ for (flag = TAILQ_FIRST(&flags->fields); flag;
+ flag = TAILQ_NEXT(flag, link))
+ if (strcasecmp(flag->field, "ikecfg") == 0) {
+ struct exchange_finalization_node *node;
+
+ node = calloc(1, (unsigned long)sizeof *node);
+ if (!node) {
+ log_print("exchange_establish_p1: "
+ "calloc (1, %lu) failed",
+ (unsigned long)sizeof(*node));
+ exchange_free(exchange);
+ return 0;
+ }
+ /*
+ * Insert this finalization inbetween
+ * the original.
+ */
+ node->first = 0;
+ node->first_arg = 0;
+ node->second_arg = name;
+ exchange_add_finalization(exchange,
+ exchange_establish_transaction,
+ node);
+ }
+ conf_free_list(flags);
+ }
+#endif
+
+ cookie_gen(msg->transport, exchange, exchange->cookies +
+ ISAKMP_HDR_ICOOKIE_LEN, ISAKMP_HDR_RCOOKIE_LEN);
+ GET_ISAKMP_HDR_ICOOKIE(msg->iov[0].iov_base, exchange->cookies);
+ exchange_enter(exchange);
+#ifdef USE_DEBUG
+ exchange_dump("exchange_setup_p1", exchange);
+#endif
+ return exchange;
+}
+
+/* Out of an incoming phase 2 message, setup an exchange. */
+struct exchange *
+exchange_setup_p2(struct message *msg, u_int8_t doi)
+{
+ struct exchange *exchange;
+ u_int8_t *buf = msg->iov[0].iov_base;
+
+ exchange = exchange_create(2, 0, doi, GET_ISAKMP_HDR_EXCH_TYPE(buf));
+ if (!exchange)
+ return 0;
+ GET_ISAKMP_HDR_ICOOKIE(buf, exchange->cookies);
+ GET_ISAKMP_HDR_RCOOKIE(buf,
+ exchange->cookies + ISAKMP_HDR_ICOOKIE_LEN);
+ GET_ISAKMP_HDR_MESSAGE_ID(buf, exchange->message_id);
+#if defined (USE_NAT_TRAVERSAL)
+ if (msg->isakmp_sa->flags & SA_FLAG_NAT_T_ENABLE)
+ exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE;
+ if (msg->isakmp_sa->flags & SA_FLAG_NAT_T_KEEPALIVE)
+ exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE;
+#endif
+ exchange_enter(exchange);
+#ifdef USE_DEBUG
+ exchange_dump("exchange_setup_p2", exchange);
+#endif
+ return exchange;
+}
+
+/* Dump interesting data about an exchange. */
+static void
+exchange_dump_real(char *header, struct exchange *exchange, int class,
+ int level)
+{
+ struct sa *sa;
+ char buf[LOG_SIZE];
+ /* Don't risk overflowing the final log buffer. */
+ size_t bufsize_max = LOG_SIZE - strlen(header) - 32;
+
+ LOG_DBG((class, level,
+ "%s: %p %s %s policy %s phase %d doi %d exchange %d step %d",
+ header, exchange, exchange->name ? exchange->name : "<unnamed>",
+ exchange->policy ? exchange->policy : "<no policy>",
+ exchange->initiator ? "initiator" : "responder", exchange->phase,
+ exchange->doi->id, exchange->type, exchange->step));
+ LOG_DBG((class, level, "%s: icookie %08x%08x rcookie %08x%08x", header,
+ decode_32(exchange->cookies), decode_32(exchange->cookies + 4),
+ decode_32(exchange->cookies + 8),
+ decode_32(exchange->cookies + 12)));
+
+ /* Include phase 2 SA list for this exchange */
+ if (exchange->phase == 2) {
+ snprintf(buf, bufsize_max, "sa_list ");
+ for (sa = TAILQ_FIRST(&exchange->sa_list);
+ sa && strlen(buf) < bufsize_max; sa = TAILQ_NEXT(sa, next))
+ snprintf(buf + strlen(buf), bufsize_max - strlen(buf),
+ "%p ", sa);
+ if (sa)
+ strlcat(buf, "...", bufsize_max);
+ } else
+ buf[0] = '\0';
+
+ LOG_DBG((class, level, "%s: msgid %08x %s", header,
+ decode_32(exchange->message_id), buf));
+}
+
+#ifdef USE_DEBUG
+static void
+exchange_dump(char *header, struct exchange *exchange)
+{
+ exchange_dump_real(header, exchange, LOG_EXCHANGE, 10);
+}
+#endif
+
+void
+exchange_report(void)
+{
+ struct exchange *exchange;
+ int i;
+
+ for (i = 0; i <= bucket_mask; i++)
+ for (exchange = LIST_FIRST(&exchange_tab[i]); exchange;
+ exchange = LIST_NEXT(exchange, link))
+ exchange_dump_real("exchange_report", exchange,
+ LOG_REPORT, 0);
+}
+
+/*
+ * Release all resources this exchange is using *except* for the "death"
+ * event. When removing an exchange from the expiration handler that event
+ * will be dealt with therein instead.
+ */
+static void
+exchange_free_aux(void *v_exch)
+{
+ struct exchange *exchange = v_exch;
+ struct sa *sa, *next_sa;
+ struct cert_handler *handler;
+
+ LOG_DBG((LOG_EXCHANGE, 80, "exchange_free_aux: freeing exchange %p",
+ exchange));
+
+ if (exchange->last_received)
+ message_free(exchange->last_received);
+ if (exchange->last_sent)
+ message_free(exchange->last_sent);
+ if (exchange->in_transit &&
+ exchange->in_transit != exchange->last_sent)
+ message_free(exchange->in_transit);
+ if (exchange->nonce_i)
+ free(exchange->nonce_i);
+ if (exchange->nonce_r)
+ free(exchange->nonce_r);
+ if (exchange->id_i)
+ free(exchange->id_i);
+ if (exchange->id_r)
+ free(exchange->id_r);
+ if (exchange->keystate)
+ free(exchange->keystate);
+ if (exchange->doi && exchange->doi->free_exchange_data)
+ exchange->doi->free_exchange_data(exchange->data);
+ if (exchange->data)
+ free(exchange->data);
+ if (exchange->name)
+ free(exchange->name);
+ if (exchange->recv_cert) {
+ handler = cert_get(exchange->recv_certtype);
+ if (handler)
+ handler->cert_free(exchange->recv_cert);
+ }
+ if (exchange->sent_cert) {
+ handler = cert_get(exchange->sent_certtype);
+ if (handler)
+ handler->cert_free(exchange->sent_cert);
+ }
+ if (exchange->recv_key)
+ key_free(exchange->recv_keytype, ISAKMP_KEYTYPE_PUBLIC,
+ exchange->recv_key);
+ if (exchange->keynote_key)
+ free(exchange->keynote_key); /* This is just a string */
+
+#if defined (POLICY) || defined (KEYNOTE)
+ if (exchange->policy_id != -1)
+ kn_close(exchange->policy_id);
+#endif
+
+ exchange_free_aca_list(exchange);
+ LIST_REMOVE(exchange, link);
+
+ /* Tell potential finalize routine we never got there. */
+ if (exchange->finalize)
+ exchange->finalize(exchange, exchange->finalize_arg, 1);
+
+ /* Remove any SAs that have not been disassociated from us. */
+ for (sa = TAILQ_FIRST(&exchange->sa_list); sa; sa = next_sa) {
+ next_sa = TAILQ_NEXT(sa, next);
+ /* One for the reference in exchange->sa_list. */
+ sa_release(sa);
+ /* And two more for the expiration and SA linked list. */
+ sa_free(sa);
+ }
+
+ free(exchange);
+}
+
+/* Release all resources this exchange is using. */
+void
+exchange_free(struct exchange *exchange)
+{
+ if (exchange->death)
+ timer_remove_event(exchange->death);
+ exchange_free_aux(exchange);
+}
+
+/*
+ * Upgrade the phase 1 exchange and its ISAKMP SA with the rcookie of our
+ * peer (found in his recently sent message MSG).
+ */
+void
+exchange_upgrade_p1(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+
+ LIST_REMOVE(exchange, link);
+ GET_ISAKMP_HDR_RCOOKIE(msg->iov[0].iov_base, exchange->cookies +
+ ISAKMP_HDR_ICOOKIE_LEN);
+ exchange_enter(exchange);
+ sa_isakmp_upgrade(msg);
+}
+
+static int
+exchange_check_old_sa(struct sa *sa, void *v_arg)
+{
+ struct sa *new_sa = v_arg;
+ char res1[1024];
+
+ if (sa == new_sa || !sa->name || !(sa->flags & SA_FLAG_READY) ||
+ (sa->flags & SA_FLAG_REPLACED))
+ return 0;
+
+ if (sa->phase != new_sa->phase || new_sa->name == 0 ||
+ strcasecmp(sa->name, new_sa->name))
+ return 0;
+
+ if (sa->initiator)
+ strlcpy(res1, ipsec_decode_ids("%s %s", sa->id_i, sa->id_i_len,
+ sa->id_r, sa->id_r_len, 0), sizeof res1);
+ else
+ strlcpy(res1, ipsec_decode_ids("%s %s", sa->id_r, sa->id_r_len,
+ sa->id_i, sa->id_i_len, 0), sizeof res1);
+
+ LOG_DBG((LOG_EXCHANGE, 30,
+ "checking whether new SA replaces existing SA with IDs %s", res1));
+
+ if (new_sa->initiator)
+ return strcasecmp(res1, ipsec_decode_ids("%s %s", new_sa->id_i,
+ new_sa->id_i_len, new_sa->id_r, new_sa->id_r_len, 0)) == 0;
+ else
+ return strcasecmp(res1, ipsec_decode_ids("%s %s", new_sa->id_r,
+ new_sa->id_r_len, new_sa->id_i, new_sa->id_i_len, 0)) == 0;
+}
+
+void
+exchange_finalize(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+ struct sa *sa, *old_sa;
+ struct proto *proto;
+ struct conf_list *attrs;
+ struct conf_list_node *attr;
+ struct cert_handler *handler;
+ int i;
+ char *id_doi, *id_trp;
+
+#ifdef USE_DEBUG
+ exchange_dump("exchange_finalize", exchange);
+#endif
+
+ /* Copy the ID from phase 1 to exchange or phase 2 SA. */
+ if (msg->isakmp_sa) {
+ if (exchange->id_i && exchange->id_r) {
+ ipsec_clone_id(&msg->isakmp_sa->id_i,
+ &msg->isakmp_sa->id_i_len, exchange->id_i,
+ exchange->id_i_len);
+ ipsec_clone_id(&msg->isakmp_sa->id_r,
+ &msg->isakmp_sa->id_r_len, exchange->id_r,
+ exchange->id_r_len);
+ } else if (msg->isakmp_sa->id_i && msg->isakmp_sa->id_r) {
+ ipsec_clone_id(&exchange->id_i, &exchange->id_i_len,
+ msg->isakmp_sa->id_i, msg->isakmp_sa->id_i_len);
+ ipsec_clone_id(&exchange->id_r, &exchange->id_r_len,
+ msg->isakmp_sa->id_r, msg->isakmp_sa->id_r_len);
+ }
+ }
+ /*
+ * Walk over all the SAs and noting them as ready. If we set the
+ * COMMIT bit, tell the peer each SA is connected.
+ *
+ * XXX The decision should really be based on if a SA was installed
+ * successfully.
+ */
+ for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
+ sa = TAILQ_NEXT(sa, next)) {
+ /* Move over the name to the SA. */
+ sa->name = exchange->name ? strdup(exchange->name) : 0;
+
+ if (exchange->flags & EXCHANGE_FLAG_I_COMMITTED) {
+ for (proto = TAILQ_FIRST(&sa->protos); proto;
+ proto = TAILQ_NEXT(proto, link))
+ for (i = 0; i < 2; i++)
+ message_send_notification(exchange->last_received,
+ msg->isakmp_sa,
+ ISAKMP_NOTIFY_STATUS_CONNECTED,
+ proto, i);
+ }
+ /*
+ * Locate any old SAs and mark them replaced
+ * (SA_FLAG_REPLACED).
+ */
+ sa->initiator = exchange->initiator;
+ while ((old_sa = sa_find(exchange_check_old_sa, sa)) != 0)
+ sa_mark_replaced(old_sa);
+
+ /* Setup the SA flags. */
+ sa->flags |= SA_FLAG_READY;
+ if (exchange->name) {
+ attrs = conf_get_list(exchange->name, "Flags");
+ if (attrs) {
+ for (attr = TAILQ_FIRST(&attrs->fields); attr;
+ attr = TAILQ_NEXT(attr, link))
+ sa->flags |= sa_flag(attr->field);
+ conf_free_list(attrs);
+ }
+ /* 'Connections' should stay alive. */
+ if (connection_exist(exchange->name)) {
+ sa->flags |= SA_FLAG_STAYALIVE;
+
+ /*
+ * ISAKMP SA of this connection should also
+ * stay alive.
+ */
+ if (exchange->phase == 2 && msg->isakmp_sa)
+ msg->isakmp_sa->flags |=
+ SA_FLAG_STAYALIVE;
+ }
+ }
+ sa->seq = exchange->seq;
+ sa->exch_type = exchange->type;
+ }
+
+ /*
+ * If this was an phase 1 SA negotiation, save the keystate in the
+ * ISAKMP SA structure for future initialization of phase 2 exchanges'
+ * keystates. Also save the Phase 1 ID and authentication
+ * information.
+ */
+ if (exchange->phase == 1 && msg->isakmp_sa) {
+ msg->isakmp_sa->keystate = exchange->keystate;
+ exchange->keystate = 0;
+
+ msg->isakmp_sa->recv_certtype = exchange->recv_certtype;
+ msg->isakmp_sa->sent_certtype = exchange->sent_certtype;
+ msg->isakmp_sa->recv_keytype = exchange->recv_keytype;
+ msg->isakmp_sa->recv_key = exchange->recv_key;
+ msg->isakmp_sa->keynote_key = exchange->keynote_key;
+ /* Reset. */
+ exchange->recv_key = 0;
+ exchange->keynote_key = 0;
+ msg->isakmp_sa->policy_id = exchange->policy_id;
+ exchange->policy_id = -1;
+ msg->isakmp_sa->initiator = exchange->initiator;
+
+ if (exchange->recv_certtype && exchange->recv_cert) {
+ handler = cert_get(exchange->recv_certtype);
+ if (handler)
+ msg->isakmp_sa->recv_cert =
+ handler->cert_dup(exchange->recv_cert);
+ }
+ if (exchange->sent_certtype) {
+ handler = cert_get(exchange->sent_certtype);
+ if (handler)
+ msg->isakmp_sa->sent_cert =
+ handler->cert_dup(exchange->sent_cert);
+ }
+ if (exchange->doi)
+ id_doi = exchange->doi->decode_ids(
+ "initiator id %s, responder id %s",
+ exchange->id_i, exchange->id_i_len,
+ exchange->id_r, exchange->id_r_len, 0);
+ else
+ id_doi = "<no doi>";
+
+ if (msg->isakmp_sa->transport)
+ id_trp =
+ msg->isakmp_sa->transport->vtbl->decode_ids(msg->isakmp_sa->transport);
+ else
+ id_trp = "<no transport>";
+
+#if defined (USE_NAT_TRAVERSAL)
+ if (exchange->flags & EXCHANGE_FLAG_NAT_T_ENABLE)
+ msg->isakmp_sa->flags |= SA_FLAG_NAT_T_ENABLE;
+ if (exchange->flags & EXCHANGE_FLAG_NAT_T_KEEPALIVE)
+ msg->isakmp_sa->flags |= SA_FLAG_NAT_T_KEEPALIVE;
+#endif
+
+ LOG_DBG((LOG_EXCHANGE, 10,
+ "exchange_finalize: phase 1 done: %s, %s", id_doi,
+ id_trp));
+
+ log_verbose("isakmpd: phase 1 done: %s, %s", id_doi, id_trp);
+ }
+ exchange->doi->finalize_exchange(msg);
+ if (exchange->finalize)
+ exchange->finalize(exchange, exchange->finalize_arg, 0);
+ exchange->finalize = 0;
+
+ /*
+ * There is no reason to keep the SAs connected to us anymore, in fact
+ * it can hurt us if we have short lifetimes on the SAs and we try
+ * to call exchange_report, where the SA list will be walked and
+ * references to freed SAs can occur.
+ */
+ while (TAILQ_FIRST(&exchange->sa_list)) {
+ sa = TAILQ_FIRST(&exchange->sa_list);
+
+ if (exchange->id_i && exchange->id_r) {
+ ipsec_clone_id(&sa->id_i, &sa->id_i_len,
+ exchange->id_i, exchange->id_i_len);
+ ipsec_clone_id(&sa->id_r, &sa->id_r_len,
+ exchange->id_r, exchange->id_r_len);
+ }
+ TAILQ_REMOVE(&exchange->sa_list, sa, next);
+ sa_release(sa);
+ }
+
+ /* If we have nothing to retransmit we can safely remove ourselves. */
+ if (!exchange->last_sent)
+ exchange_free(exchange);
+}
+
+/* Stash a nonce into the exchange data. */
+static int
+exchange_nonce(struct exchange *exchange, int peer, size_t nonce_sz,
+ u_int8_t *buf)
+{
+ u_int8_t **nonce;
+ size_t *nonce_len;
+ int initiator = exchange->initiator ^ peer;
+ char header[32];
+
+ nonce = initiator ? &exchange->nonce_i : &exchange->nonce_r;
+ nonce_len =
+ initiator ? &exchange->nonce_i_len : &exchange->nonce_r_len;
+ *nonce_len = nonce_sz;
+ *nonce = malloc(nonce_sz);
+ if (!*nonce) {
+ log_error("exchange_nonce: malloc (%lu) failed",
+ (unsigned long)nonce_sz);
+ return -1;
+ }
+ memcpy(*nonce, buf, nonce_sz);
+ snprintf(header, sizeof header, "exchange_nonce: NONCE_%c",
+ initiator ? 'i' : 'r');
+ LOG_DBG_BUF((LOG_EXCHANGE, 80, header, *nonce, nonce_sz));
+ return 0;
+}
+
+/* Generate our NONCE. */
+int
+exchange_gen_nonce(struct message *msg, size_t nonce_sz)
+{
+ struct exchange *exchange = msg->exchange;
+ u_int8_t *buf;
+
+ buf = malloc(ISAKMP_NONCE_SZ + nonce_sz);
+ if (!buf) {
+ log_error("exchange_gen_nonce: malloc (%lu) failed",
+ ISAKMP_NONCE_SZ + (unsigned long)nonce_sz);
+ return -1;
+ }
+ getrandom(buf + ISAKMP_NONCE_DATA_OFF, nonce_sz);
+ if (message_add_payload(msg, ISAKMP_PAYLOAD_NONCE, buf,
+ ISAKMP_NONCE_SZ + nonce_sz, 1)) {
+ free(buf);
+ return -1;
+ }
+ return exchange_nonce(exchange, 0, nonce_sz,
+ buf + ISAKMP_NONCE_DATA_OFF);
+}
+
+/* Save the peer's NONCE. */
+int
+exchange_save_nonce(struct message *msg)
+{
+ struct payload *noncep;
+ struct exchange *exchange = msg->exchange;
+
+ noncep = payload_first(msg, ISAKMP_PAYLOAD_NONCE);
+ noncep->flags |= PL_MARK;
+ return exchange_nonce(exchange, 1, GET_ISAKMP_GEN_LENGTH(noncep->p) -
+ ISAKMP_NONCE_DATA_OFF, noncep->p + ISAKMP_NONCE_DATA_OFF);
+}
+
+/* Save the peer's CERT REQuests. */
+int
+exchange_save_certreq(struct message *msg)
+{
+ struct payload *cp = payload_first(msg, ISAKMP_PAYLOAD_CERT_REQ);
+ struct exchange *exchange = msg->exchange;
+ struct certreq_aca *aca;
+
+ for (; cp; cp = TAILQ_NEXT(cp, link)) {
+ cp->flags |= PL_MARK;
+ aca = certreq_decode(GET_ISAKMP_CERTREQ_TYPE(cp->p), cp->p +
+ ISAKMP_CERTREQ_AUTHORITY_OFF, GET_ISAKMP_GEN_LENGTH(cp->p)
+ - ISAKMP_CERTREQ_AUTHORITY_OFF);
+ if (aca)
+ TAILQ_INSERT_TAIL(&exchange->aca_list, aca, link);
+ }
+
+ return 0;
+}
+
+/* Free the list of pending CERTREQs. */
+void
+exchange_free_aca_list(struct exchange *exchange)
+{
+ struct certreq_aca *aca;
+
+ for (aca = TAILQ_FIRST(&exchange->aca_list); aca;
+ aca = TAILQ_FIRST(&exchange->aca_list)) {
+ if (aca->data) {
+ if (aca->handler)
+ aca->handler->free_aca(aca->data);
+ free(aca->data);
+ }
+ TAILQ_REMOVE(&exchange->aca_list, aca, link);
+ free(aca);
+ }
+}
+
+/* Obtain certificates from acceptable certification authority. */
+int
+exchange_add_certs(struct message *msg)
+{
+ struct exchange *exchange = msg->exchange;
+ struct certreq_aca *aca;
+ u_int8_t *cert = 0, *new_cert = 0;
+ u_int32_t certlen;
+ u_int8_t *id;
+ size_t id_len;
+
+ id = exchange->initiator ? exchange->id_r : exchange->id_i;
+ id_len = exchange->initiator ? exchange->id_r_len : exchange->id_i_len;
+
+ /*
+ * Without IDs we cannot handle this yet. Keep the aca_list around for
+ * a later step/retry to see if we got the ID by then.
+ * Note: A 'return -1' breaks X509-auth interop in the responder case
+ * with some IPsec clients that send CERTREQs early (such as
+ * the SSH Sentinel).
+ */
+ if (!id)
+ return 0;
+
+ for (aca = TAILQ_FIRST(&exchange->aca_list); aca;
+ aca = TAILQ_NEXT(aca, link)) {
+ /* XXX? If we can not satisfy a CERTREQ we drop the message. */
+ if (!aca->handler->cert_obtain(id, id_len, aca->data, &cert,
+ &certlen)) {
+ log_print("exchange_add_certs: could not obtain cert "
+ "for a type %d cert request", aca->id);
+ if (cert)
+ free(cert);
+ return -1;
+ }
+ new_cert = realloc(cert, ISAKMP_CERT_SZ + certlen);
+ if (!new_cert) {
+ log_error("exchange_add_certs: realloc (%p, %d) "
+ "failed", cert, ISAKMP_CERT_SZ + certlen);
+ if (cert)
+ free(cert);
+ return -1;
+ }
+ cert = new_cert;
+ memmove(cert + ISAKMP_CERT_DATA_OFF, cert, certlen);
+ SET_ISAKMP_CERT_ENCODING(cert, aca->id);
+ if (message_add_payload(msg, ISAKMP_PAYLOAD_CERT, cert,
+ ISAKMP_CERT_SZ + certlen, 1)) {
+ free(cert);
+ return -1;
+ }
+ }
+
+ /* We dont need the CERT REQs any more, they are answered. */
+ exchange_free_aca_list(exchange);
+
+ return 0;
+}
+
+static void
+exchange_establish_finalize(struct exchange *exchange, void *arg, int fail)
+{
+ char *name = arg;
+
+ LOG_DBG((LOG_EXCHANGE, 20, "exchange_establish_finalize: "
+ "finalizing exchange %p with arg %p (%s) & fail = %d",
+ exchange, arg, name ? name : "<unnamed>", fail));
+
+ if (!fail)
+ exchange_establish(name, 0, 0);
+ free(name);
+}
+
+/*
+ * Establish an exchange named NAME, and record the FINALIZE function
+ * taking ARG as an argument to be run after the exchange is ready.
+ */
+void
+exchange_establish(char *name, void (*finalize)(struct exchange *, void *,
+ int), void *arg)
+{
+ struct transport *transport;
+ struct sa *isakmp_sa;
+ struct exchange *exchange;
+ int phase;
+ char *trpt, *peer;
+
+ phase = conf_get_num(name, "Phase", 0);
+
+ /*
+ * First of all, never try to establish anything if another exchange
+ * of the same kind is running.
+ */
+ exchange = exchange_lookup_by_name(name, phase);
+ if (exchange) {
+ LOG_DBG((LOG_EXCHANGE, 40,
+ "exchange_establish: %s exchange already exists as %p",
+ name, exchange));
+ exchange_add_finalization(exchange, finalize, arg);
+ return;
+ }
+ switch (phase) {
+ case 1:
+ trpt = conf_get_str(name, "Transport");
+ if (!trpt) {
+ /* Phase 1 transport defaults to "udp". */
+ trpt = ISAKMP_DEFAULT_TRANSPORT;
+ }
+ transport = transport_create(trpt, name);
+ if (!transport) {
+ log_print("exchange_establish: transport \"%s\" for "
+ "peer \"%s\" could not be created", trpt, name);
+ return;
+ }
+ exchange_establish_p1(transport, 0, 0, name, 0, finalize, arg);
+ break;
+
+ case 2:
+ peer = conf_get_str(name, "ISAKMP-peer");
+ if (!peer) {
+ log_print("exchange_establish: No ISAKMP-peer given "
+ "for \"%s\"", name);
+ return;
+ }
+ isakmp_sa = sa_lookup_by_name(peer, 1);
+ if (!isakmp_sa) {
+ name = strdup(name);
+ if (!name) {
+ log_error("exchange_establish: "
+ "strdup (\"%s\") failed", name);
+ return;
+ }
+ if (conf_get_num(peer, "Phase", 0) != 1) {
+ log_print("exchange_establish: "
+ "[%s]:ISAKMP-peer's (%s) phase is not 1",
+ name, peer);
+ return;
+ }
+ /*
+ * XXX We're losing information here (what the
+ * original finalize routine was. As a result, if an
+ * exchange does not manage to get through, there may
+ * be application-specific information that won't get
+ * cleaned up, since no error signalling will be done.
+ * This is the case with dynamic SAs and PFKEY.
+ */
+ exchange_establish(peer, exchange_establish_finalize,
+ name);
+ exchange = exchange_lookup_by_name(peer, 1);
+ /*
+ * If the exchange was correctly initialized, add the
+ * original finalization routine; otherwise, call it
+ * directly.
+ */
+ if (exchange)
+ exchange_add_finalization(exchange, finalize,
+ arg);
+ else
+ finalize(0, arg, 1); /* Indicate failure */
+ return;
+ } else
+ exchange_establish_p2(isakmp_sa, 0, name, 0, finalize,
+ arg);
+ break;
+
+ default:
+ log_print("exchange_establish: "
+ "peer \"%s\" does not have a correct phase (%d)",
+ name, phase);
+ break;
+ }
+}