src/sbin/isakmpd/exchange.c

1861 lines
52 KiB
C

/* $OpenBSD: exchange.c,v 1.142 2018/01/15 09:54:48 mpi 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 <regex.h>
#include <keynote.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"
#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 *);
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:
/*
* Don't retransmit responses for
* unauthenticated messages.
*/
if ((exchange->type == ISAKMP_EXCH_ID_PROT ||
exchange->type == ISAKMP_EXCH_AGGRESSIVE) &&
exchange->phase == 1 && exchange->step == 1)
msg->flags |= MSG_DONTRETRANSMIT;
/* 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 = calloc(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]);
}
/* 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);
exchange->linked = 1;
}
/*
* 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 timespec 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;
}
}
clock_gettime(CLOCK_MONOTONIC, &expiration);
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. */
int
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 -1;
}
/* 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 -1;
}
type = constant_value(isakmp_exch_cst, str);
if (!type) {
log_print("exchange_establish_p1: "
"unknown exchange type %s", str);
return -1;
}
}
}
exchange = exchange_create(1, 1, doi, type);
if (!exchange) {
return -1;
}
if (name) {
exchange->name = strdup(name);
if (!exchange->name) {
log_error("exchange_establish_p1: "
"strdup (\"%s\") failed", name);
exchange_free(exchange);
return -1;
}
}
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 -1;
}
/*
* 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 0; /* exchange_free() runs finalize */
}
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) {
message_free(msg);
exchange_free(exchange);
return 0; /* exchange_free() runs finalize */
}
sa_reference(msg->isakmp_sa);
if (stayalive)
msg->isakmp_sa->flags |= SA_FLAG_STAYALIVE;
}
msg->extra = args;
exchange_run(msg);
return 0;
}
/* Establish a phase 2 exchange. XXX With just one SA for now. */
int
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 -1;
}
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 -1;
}
/* 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 -1;
}
/* XXX IKE dependent. */
type = constant_value(ike_exch_cst, str);
if (!type) {
log_print("exchange_establish_p2: unknown "
"exchange type %s", str);
return -1;
}
}
}
exchange = exchange_create(2, 1, doi, type);
if (!exchange) {
return -1;
}
if (name) {
exchange->name = strdup(name);
if (!exchange->name) {
log_error("exchange_establish_p2: "
"strdup (\"%s\") failed", name);
exchange_free(exchange);
return -1;
}
}
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);
arc4random_buf(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 0; /* exchange_free() runs finalize */
}
}
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);
return 0;
}
/* 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).
*/
exchange = exchange_lookup_active(name, 1);
if (exchange) {
LOG_DBG((LOG_EXCHANGE, 40,
"exchange_establish: %s exchange already "
"exists as %p", name, exchange));
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 : "<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));
}
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);
free(exchange->nonce_i);
free(exchange->nonce_r);
free(exchange->id_i);
free(exchange->id_r);
free(exchange->keystate);
if (exchange->data) {
if (exchange->doi && exchange->doi->free_exchange_data)
exchange->doi->free_exchange_data(exchange->data);
free(exchange->data);
}
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);
free(exchange->keynote_key); /* This is just a string */
if (exchange->policy_id != -1)
kn_close(exchange->policy_id);
exchange_free_aca_list(exchange);
if (exchange->linked) {
LIST_REMOVE(exchange, link);
exchange->linked = 0;
}
/* 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);
exchange->linked = 0;
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 = "<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 (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, %s",
(exchange->initiator == 0) ? " (as responder)" : "",
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;
}
arc4random_buf(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)) {
free(aca->raw_ca);
if (aca->data) {
if (aca->handler)
aca->handler->free_aca(aca->data);
free(aca->data);
}
TAILQ_REMOVE(&exchange->aca_list, aca, link);
free(aca);
}
}
/* Add any CERTREQs we should send. */
int
exchange_add_certreqs(struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct certreq_aca *aca;
u_int8_t *buf;
/*
* Some peers (e.g. Cisco IOS) won't send their cert unless we
* specifically ask beforehand with CERTREQ. We reflect any
* CERTREQs we receive from the initiator in order to do this.
* This avoids leaking information about which CAs we trust,
* and works in the most common case where both ends trust the
* same CA.
*/
for (aca = TAILQ_FIRST(&exchange->aca_list); aca;
aca = TAILQ_NEXT(aca, link)) {
/* But only do this if we have at least one CA */
if (aca->handler != NULL && aca->handler->ca_count() == 0) {
LOG_DBG((LOG_EXCHANGE, 10,
"exchange_add_certreqs: no CA, so not "
"sending a CERTREQ"));
continue;
}
if (aca->raw_ca_len) {
buf = malloc(ISAKMP_CERTREQ_SZ + aca->raw_ca_len);
if (buf == NULL) {
log_error("exchange_add_certreqs: "
"malloc (%lu) failed",
ISAKMP_CERTREQ_SZ +
(unsigned long)aca->raw_ca_len);
return -1;
}
buf[ISAKMP_CERTREQ_TYPE_OFF] = aca->id;
memcpy(buf + ISAKMP_CERTREQ_AUTHORITY_OFF,
aca->raw_ca, aca->raw_ca_len);
if (message_add_payload(msg, ISAKMP_PAYLOAD_CERT_REQ,
buf, ISAKMP_CERTREQ_SZ + aca->raw_ca_len, 1)) {
free(buf);
return -1;
}
}
}
return 0;
}
/* 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);
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);
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 : "<unnamed>", 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));
if (finalize)
finalize(0, arg, 1);
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);
if (finalize)
finalize(0, arg, 1);
return;
}
if (exchange_establish_p1(transport, 0, 0, name, 0, finalize,
arg, stayalive) < 0 && finalize)
finalize(0, arg, 1);
break;
case 2:
peer = conf_get_str(name, "ISAKMP-peer");
if (!peer) {
log_print("exchange_establish: No ISAKMP-peer given "
"for \"%s\"", name);
if (finalize)
finalize(0, arg, 1);
return;
}
isakmp_sa = sa_lookup_by_name(peer, 1);
if (!isakmp_sa) {
/* freed by exchange_establish_finalize() */
name = strdup(name);
if (!name) {
log_error("exchange_establish: "
"strdup (\"%s\") failed", name);
if (finalize)
finalize(0, arg, 1);
return;
}
if (conf_get_num(peer, "Phase", 0) != 1) {
log_print("exchange_establish: "
"[%s]:ISAKMP-peer's (%s) phase is not 1",
name, peer);
if (finalize)
finalize(0, arg, 1);
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);
}
return;
} else {
if (exchange_establish_p2(isakmp_sa, 0, name, 0,
finalize, arg) < 0 && finalize)
finalize(0, arg, 1);
}
break;
default:
log_print("exchange_establish: "
"peer \"%s\" does not have a correct phase (%d)",
name, phase);
break;
}
}