/* $OpenBSD: exchange.c,v 1.129 2007/03/03 10:29:18 tom 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 #include #include #include #include #include #include #include #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" #include "isakmp_cfg.h" #include "libcrypto.h" #include "log.h" #include "message.h" #include "timer.h" #include "transport.h" #include "ipsec.h" #include "sa.h" #include "ui.h" #include "util.h" #include "key.h" #include "dpd.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 static void exchange_dump(char *, struct exchange *); 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 }; 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 }; 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; case ISAKMP_EXCH_AGGRESSIVE: return script_aggressive; case ISAKMP_EXCH_INFO: return script_informational; case ISAKMP_EXCH_TRANSACTION: return script_transaction; default: if (exchange->type >= ISAKMP_EXCH_DOI_MIN) 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 dialogue. 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 < ISAKMP_PAYLOAD_MAX; i++) { if (i == ISAKMP_PAYLOAD_PROPOSAL || i == ISAKMP_PAYLOAD_TRANSFORM) continue; TAILQ_FOREACH(p, &msg->payload[i], 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_handle_leftover_payloads: " "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 : "", 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 : "", 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; bzero(exchange->cookies, ISAKMP_HDR_COOKIES_LEN); bzero(exchange->message_id, 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; } 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); } /* 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, int stayalive) { struct exchange *exchange; struct message *msg; struct conf_list *flags; struct conf_list_node *flag; 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_establish_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; 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); } exchange_add_finalization(exchange, finalize, arg); cookie_gen(t, exchange, exchange->cookies, ISAKMP_HDR_ICOOKIE_LEN); exchange_enter(exchange); exchange_dump("exchange_establish_p1", exchange); 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); if (stayalive) msg->isakmp_sa->flags |= SA_FLAG_STAYALIVE; } 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 (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; exchange_enter(exchange); exchange_dump("exchange_establish_p2", exchange); /* * 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; struct conf_list *flags; struct conf_list_node *flag; 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; str = "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; 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); } 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); exchange_dump("exchange_setup_p1", exchange); 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 (msg->isakmp_sa && (msg->isakmp_sa->flags & SA_FLAG_NAT_T_ENABLE)) exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE; if (msg->isakmp_sa && (msg->isakmp_sa->flags & SA_FLAG_NAT_T_KEEPALIVE)) exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE; exchange_enter(exchange); exchange_dump("exchange_setup_p2", exchange); 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 : "", exchange->policy ? exchange->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)); } static void exchange_dump(char *header, struct exchange *exchange) { exchange_dump_real(header, exchange, LOG_EXCHANGE, 10); } 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 (exchange->policy_id != -1) kn_close(exchange->policy_id); 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; exchange_dump("exchange_finalize", exchange); /* 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 = ""; if (msg->isakmp_sa->transport) id_trp = msg->isakmp_sa->transport->vtbl->decode_ids(msg->isakmp_sa->transport); else id_trp = ""; 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; 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); } /* * Start sending DPD messages after all SAs have been released. * Otherwise we have a race between exchange_free_aux() and * dpd_check_event() where both will call sa_free(). */ if (exchange->phase == 1 && msg->isakmp_sa && (exchange->flags & EXCHANGE_FLAG_DPD_CAP_PEER)) dpd_start(msg->isakmp_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]; if (nonce_sz < 8 || nonce_sz > 256) { /* * RFC2409, ch 5: The length of nonce payload MUST be * between 8 and 256 bytes inclusive. * XXX I'm assuming the generic payload header is not included. */ LOG_DBG((LOG_EXCHANGE, 20, "exchange_nonce: invalid nonce length %lu", (unsigned long)nonce_sz)); return -1; } 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; struct exchange *exchange = msg->exchange; struct certreq_aca *aca; TAILQ_FOREACH(cp, &msg->payload[ISAKMP_PAYLOAD_CERT_REQ], 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 need to reset cert here, as it is now controlled by * message_add_payload() (i.e. we must not free() it), and * it is possible for the next iteration of the aca loop * to fail early in cert_obtain before it writes to &cert. */ cert = NULL; } /* 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 : "", fail)); if (!fail) exchange_establish(name, 0, 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, int stayalive) { struct transport *transport; struct sa *isakmp_sa; struct exchange *exchange; int phase; char *trpt, *peer; phase = conf_get_num(name, "Phase", 0); if (ui_daemon_passive) { LOG_DBG((LOG_EXCHANGE, 40, "exchange_establish:" " returning in passive mode for exchange %s phase %d", name, phase)); return; } /* * 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, stayalive); 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); free(name); 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 signaling will be done. * This is the case with dynamic SAs and PFKEY. */ exchange_establish(peer, exchange_establish_finalize, name, 0); 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 { /* Indicate failure */ if (finalize) finalize(0, arg, 1); free(name); } 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; } }