#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"
#define INITIAL_BUCKET_BITS 6
#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;
static int bucket_mask;
int16_t script_base[] = {
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_NONCE,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_NONCE,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_END
};
int16_t script_identity_protection[] = {
ISAKMP_PAYLOAD_SA,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_SA,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_NONCE,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_NONCE,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_END
};
int16_t script_authentication_only[] = {
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_NONCE,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_NONCE,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_END
};
int16_t script_aggressive[] = {
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_NONCE,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_SWITCH,
ISAKMP_PAYLOAD_SA,
ISAKMP_PAYLOAD_KEY_EXCH,
ISAKMP_PAYLOAD_NONCE,
ISAKMP_PAYLOAD_ID,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_SWITCH,
EXCHANGE_SCRIPT_AUTH,
EXCHANGE_SCRIPT_END
};
int16_t script_informational[] = {
EXCHANGE_SCRIPT_INFO,
EXCHANGE_SCRIPT_END
};
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;
}
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)));
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))))) {
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)
return 1;
return 0;
}
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)));
}
}
}
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) {
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)) {
log_print("exchange_run: doi->%s (%p) failed",
exchange->initiator ? "initiator" :
"responder", msg);
message_free(msg);
return;
}
switch (exchange_validate(msg)) {
case 1:
msg->flags |= MSG_LAST;
if (exchange->step > 0) {
if (exchange->last_sent)
message_free(exchange->last_sent);
exchange->last_sent = msg;
}
message_register_post_send(msg,
exchange_finalize);
case 0:
if ((exchange->type == ISAKMP_EXCH_ID_PROT ||
exchange->type == ISAKMP_EXCH_AGGRESSIVE) &&
exchange->phase == 1 && exchange->step == 1)
msg->flags |= MSG_DONTRETRANSMIT;
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:
if (handler(msg)) {
message_free(msg);
return;
}
exchange_handle_leftover_payloads(msg);
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");
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]);
}
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;
}
struct exchange *
exchange_lookup_by_name(char *name, int phase)
{
struct exchange *exchange;
int i;
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));
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;
}
static struct exchange *
exchange_lookup_active(char *name, int phase)
{
struct exchange *exchange;
int i;
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;
for (i = 0; i < ISAKMP_HDR_COOKIES_LEN; i += 2) {
cp = exchange->cookies + i;
bucket ^= cp[0] | cp[1] << 8;
}
for (i = 0; i < ISAKMP_HDR_MESSAGE_ID_LEN; i += 2) {
cp = exchange->message_id + i;
bucket ^= cp[0] | cp[1] << 8;
}
bucket &= bucket_mask;
LIST_INSERT_HEAD(&exchange_tab[bucket], exchange, link);
exchange->linked = 1;
}
struct exchange *
exchange_lookup(u_int8_t *msg, int phase2)
{
struct exchange *exchange;
u_int16_t bucket = 0;
u_int8_t *cp;
int i;
for (i = 0; i < ISAKMP_HDR_COOKIES_LEN; i += 2) {
cp = msg + ISAKMP_HDR_COOKIES_OFF + i;
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;
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;
}
static struct exchange *
exchange_create(int phase, int initiator, int doi, int type)
{
struct exchange *exchange;
struct timespec expiration;
int delta;
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);
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) {
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;
};
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);
}
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)
{
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);
}
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 (type == 0) {
tag = conf_get_str(name, "Configuration");
if (!tag) {
tag = CONF_DFLT_TAG_PHASE1_CONFIG;
}
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;
}
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;
}
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;
}
msg->exchange = exchange;
if (exchange->type != ISAKMP_EXCH_INFO &&
exchange->type != ISAKMP_EXCH_TRANSACTION) {
sa_create(exchange, 0);
msg->isakmp_sa = TAILQ_FIRST(&exchange->sa_list);
if (!msg->isakmp_sa) {
message_free(msg);
exchange_free(exchange);
return 0;
}
sa_reference(msg->isakmp_sa);
if (stayalive)
msg->isakmp_sa->flags |= SA_FLAG_STAYALIVE;
}
msg->extra = args;
exchange_run(msg);
return 0;
}
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) {
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);
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;
}
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;
}
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);
if (exchange->type != ISAKMP_EXCH_INFO &&
exchange->type != ISAKMP_EXCH_TRANSACTION) {
for (i = 0; i < 1; i++)
if (sa_create(exchange, isakmp_sa->transport)) {
exchange_free(exchange);
return 0;
}
}
msg = message_alloc(isakmp_sa->transport, 0, ISAKMP_HDR_SZ);
msg->isakmp_sa = isakmp_sa;
sa_reference(isakmp_sa);
msg->extra = args;
msg->exchange = exchange;
exchange_run(msg);
return 0;
}
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;
type = GET_ISAKMP_HDR_EXCH_TYPE(msg->iov[0].iov_base);
if (type != ISAKMP_EXCH_INFO) {
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) {
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;
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) {
log_print("exchange_setup_p1: expected %s DOI", str);
return 0;
}
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;
}
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;
}
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;
}
static void
exchange_dump_real(char *header, struct exchange *exchange, int class,
int level)
{
struct sa *sa;
char buf[LOG_SIZE];
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)));
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);
}
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);
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;
}
if (exchange->finalize)
exchange->finalize(exchange, exchange->finalize_arg, 1);
for (sa = TAILQ_FIRST(&exchange->sa_list); sa; sa = next_sa) {
next_sa = TAILQ_NEXT(sa, next);
sa_release(sa);
sa_free(sa);
}
free(exchange);
}
void
exchange_free(struct exchange *exchange)
{
if (exchange->death)
timer_remove_event(exchange->death);
exchange_free_aux(exchange);
}
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);
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);
}
}
for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
sa = TAILQ_NEXT(sa, next)) {
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);
}
sa->initiator = exchange->initiator;
while ((old_sa = sa_find(exchange_check_old_sa, sa)) != 0)
sa_mark_replaced(old_sa);
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);
}
if (connection_exist(exchange->name)) {
sa->flags |= SA_FLAG_STAYALIVE;
if (exchange->phase == 2 && msg->isakmp_sa)
msg->isakmp_sa->flags |=
SA_FLAG_STAYALIVE;
}
}
sa->seq = exchange->seq;
sa->exch_type = exchange->type;
}
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;
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;
while (TAILQ_FIRST(&exchange->sa_list)) {
sa = TAILQ_FIRST(&exchange->sa_list);
if (exchange->id_i && exchange->id_r) {
ipsec_clone_id(&sa->id_i, &sa->id_i_len,
exchange->id_i, exchange->id_i_len);
ipsec_clone_id(&sa->id_r, &sa->id_r_len,
exchange->id_r, exchange->id_r_len);
}
TAILQ_REMOVE(&exchange->sa_list, sa, next);
sa_release(sa);
}
if (exchange->phase == 1 && msg->isakmp_sa &&
(exchange->flags & EXCHANGE_FLAG_DPD_CAP_PEER))
dpd_start(msg->isakmp_sa);
if (!exchange->last_sent)
exchange_free(exchange);
}
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) {
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;
}
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);
}
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);
}
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;
}
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);
}
}
int
exchange_add_certreqs(struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct certreq_aca *aca;
u_int8_t *buf;
for (aca = TAILQ_FIRST(&exchange->aca_list); aca;
aca = TAILQ_NEXT(aca, link)) {
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;
}
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;
if (!id)
return 0;
for (aca = TAILQ_FIRST(&exchange->aca_list); aca;
aca = TAILQ_NEXT(aca, link)) {
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;
}
cert = NULL;
}
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);
}
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;
}
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) {
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) {
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;
}
exchange_establish(peer, exchange_establish_finalize,
name, 0);
exchange = exchange_lookup_by_name(peer, 1);
if (exchange)
exchange_add_finalization(exchange, finalize,
arg);
else {
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;
}
}