diff options
Diffstat (limited to 'keyexchange/isakmpd-20041012/exchange.c')
-rw-r--r-- | keyexchange/isakmpd-20041012/exchange.c | 1799 |
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; + } +} |