From 6dc4f1912caf7f01f4b977ff8aaa50be61db2aba Mon Sep 17 00:00:00 2001 From: Othmar Gsenger Date: Thu, 27 Dec 2007 11:13:13 +0000 Subject: adden new isakmpd --- keyexchange/isakmpd-20041012/sa.c | 1247 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1247 insertions(+) create mode 100644 keyexchange/isakmpd-20041012/sa.c (limited to 'keyexchange/isakmpd-20041012/sa.c') diff --git a/keyexchange/isakmpd-20041012/sa.c b/keyexchange/isakmpd-20041012/sa.c new file mode 100644 index 0000000..14c7857 --- /dev/null +++ b/keyexchange/isakmpd-20041012/sa.c @@ -0,0 +1,1247 @@ +/* $OpenBSD: sa.c,v 1.86 2004/08/10 15:59:10 ho Exp $ */ +/* $EOM: sa.c,v 1.112 2000/12/12 00:22:52 niklas Exp $ */ + +/* + * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. + * Copyright (c) 1999, 2001 Angelos D. Keromytis. All rights reserved. + * Copyright (c) 2003, 2004 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 + +#if defined (USE_KEYNOTE) || defined (USE_POLICY) +#include +#include +#endif /* USE_KEYNOTE || USE_POLICY */ + +#include "sysdep.h" + +#include "attribute.h" +#include "conf.h" +#include "connection.h" +#include "cookie.h" +#include "doi.h" +#include "exchange.h" +#include "isakmp.h" +#include "log.h" +#include "message.h" +#include "monitor.h" +#include "sa.h" +#include "timer.h" +#include "transport.h" +#include "util.h" +#include "cert.h" +#include "policy.h" +#include "key.h" +#include "ipsec.h" +#include "ipsec_num.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 + +#if 0 +static void sa_resize(void); +#endif +static void sa_soft_expire(void *); +static void sa_hard_expire(void *); + +static LIST_HEAD(sa_list, sa) *sa_tab; + +/* Works both as a maximum index and a mask. */ +static int bucket_mask; + +void +sa_init(void) +{ + int i; + + bucket_mask = (1 << INITIAL_BUCKET_BITS) - 1; + sa_tab = malloc((bucket_mask + 1) * sizeof(struct sa_list)); + if (!sa_tab) + log_fatal("sa_init: malloc (%lu) failed", + (bucket_mask + 1) * (unsigned long)sizeof(struct sa_list)); + for (i = 0; i <= bucket_mask; i++) + LIST_INIT(&sa_tab[i]); +} + +#if 0 +/* XXX We don't yet resize. */ +static void +sa_resize(void) +{ + int new_mask = (bucket_mask + 1) * 2 - 1; + int i; + struct sa_list *new_tab; + + new_tab = realloc(sa_tab, (new_mask + 1) * sizeof(struct sa_list)); + if (!new_tab) + return; + sa_tab = new_tab; + for (i = bucket_mask + 1; i <= new_mask; i++) + LIST_INIT(&sa_tab[i]); + bucket_mask = new_mask; + + /* XXX Rehash existing entries. */ +} +#endif + +/* Lookup an SA with the help from a user-supplied checking function. */ +struct sa * +sa_find(int (*check) (struct sa*, void *), void *arg) +{ + int i; + struct sa *sa; + + for (i = 0; i <= bucket_mask; i++) + for (sa = LIST_FIRST(&sa_tab[i]); sa; sa = LIST_NEXT(sa, link)) + if (check(sa, arg)) { + LOG_DBG((LOG_SA, 90, "sa_find: return SA %p", + sa)); + return sa; + } + LOG_DBG((LOG_SA, 90, "sa_find: no SA matched query")); + return 0; +} + +/* Check if SA is an ISAKMP SA with an initiator cookie equal to ICOOKIE. */ +static int +sa_check_icookie(struct sa *sa, void *icookie) +{ + return sa->phase == 1 && + memcmp(sa->cookies, icookie, ISAKMP_HDR_ICOOKIE_LEN) == 0; +} + +/* Lookup an ISAKMP SA out of just the initiator cookie. */ +struct sa * +sa_lookup_from_icookie(u_int8_t *cookie) +{ + return sa_find(sa_check_icookie, cookie); +} + +struct name_phase_arg { + char *name; + u_int8_t phase; +}; + +/* Check if SA has the name and phase given by V_ARG. */ +static int +sa_check_name_phase(struct sa *sa, void *v_arg) +{ + struct name_phase_arg *arg = v_arg; + + return sa->name && strcasecmp(sa->name, arg->name) == 0 && + sa->phase == arg->phase && !(sa->flags & SA_FLAG_REPLACED); +} + +/* Lookup an SA by name, case-independent, and phase. */ +struct sa * +sa_lookup_by_name(char *name, int phase) +{ + struct name_phase_arg arg; + + arg.name = name; + arg.phase = phase; + return sa_find(sa_check_name_phase, &arg); +} + +struct addr_arg { + struct sockaddr *addr; + socklen_t len; + int phase; + int flags; +}; + +/* + * Check if SA is ready and has a peer with an address equal the one given + * by V_ADDR. Furthermore if we are searching for a specific phase, check + * that too. + */ +static int +sa_check_peer(struct sa *sa, void *v_addr) +{ + struct addr_arg *addr = v_addr; + struct sockaddr *dst; + + if (!sa->transport || (sa->flags & SA_FLAG_READY) == 0 || + (addr->phase && addr->phase != sa->phase)) + return 0; + + sa->transport->vtbl->get_dst(sa->transport, &dst); + return sysdep_sa_len(dst) == addr->len && + memcmp(dst, addr->addr, sysdep_sa_len(dst)) == 0; +} + +struct dst_isakmpspi_arg { + struct sockaddr *dst; + u_int8_t *spi; /* must be ISAKMP_SPI_SIZE octets */ +}; + +/* + * Check if SA matches what we are asking for through V_ARG. It has to + * be a finished phaes 1 (ISAKMP) SA. + */ +static int +isakmp_sa_check(struct sa *sa, void *v_arg) +{ + struct dst_isakmpspi_arg *arg = v_arg; + struct sockaddr *dst, *src; + + if (sa->phase != 1 || !(sa->flags & SA_FLAG_READY)) + return 0; + + /* verify address is either src or dst for this sa */ + sa->transport->vtbl->get_dst(sa->transport, &dst); + sa->transport->vtbl->get_src(sa->transport, &src); + if (memcmp(src, arg->dst, sysdep_sa_len(src)) && + memcmp(dst, arg->dst, sysdep_sa_len(dst))) + return 0; + + /* match icookie+rcookie against spi */ + if (memcmp(sa->cookies, arg->spi, ISAKMP_HDR_COOKIES_LEN) == 0) + return 1; + + return 0; +} + +/* + * Find an ISAKMP SA with a "name" of DST & SPI. + */ +struct sa * +sa_lookup_isakmp_sa(struct sockaddr *dst, u_int8_t *spi) +{ + struct dst_isakmpspi_arg arg; + + arg.dst = dst; + arg.spi = spi; + + return sa_find(isakmp_sa_check, &arg); +} + +/* Lookup a ready SA by the peer's address. */ +struct sa * +sa_lookup_by_peer(struct sockaddr *dst, socklen_t dstlen) +{ + struct addr_arg arg; + + arg.addr = dst; + arg.len = dstlen; + arg.phase = 0; + + return sa_find(sa_check_peer, &arg); +} + +/* Lookup a ready ISAKMP SA given its peer address. */ +struct sa * +sa_isakmp_lookup_by_peer(struct sockaddr *dst, socklen_t dstlen) +{ + struct addr_arg arg; + + arg.addr = dst; + arg.len = dstlen; + arg.phase = 1; + + return sa_find(sa_check_peer, &arg); +} + +int +sa_enter(struct sa *sa) +{ + u_int16_t bucket = 0; + int i; + u_int8_t *cp; + + /* XXX We might resize if we are crossing a certain threshold */ + + for (i = 0; i < ISAKMP_HDR_COOKIES_LEN; i += 2) { + cp = sa->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 = sa->message_id + i; + /* Doing it this way avoids alignment problems. */ + bucket ^= cp[0] | cp[1] << 8; + } + bucket &= bucket_mask; + LIST_INSERT_HEAD(&sa_tab[bucket], sa, link); + sa_reference(sa); + LOG_DBG((LOG_SA, 70, "sa_enter: SA %p added to SA list", sa)); + return 1; +} + +/* + * Lookup the SA given by the header fields MSG. PHASE2 is false when + * looking for phase 1 SAa and true otherwise. + */ +struct sa * +sa_lookup_by_header(u_int8_t *msg, int phase2) +{ + return sa_lookup(msg + ISAKMP_HDR_COOKIES_OFF, + phase2 ? msg + ISAKMP_HDR_MESSAGE_ID_OFF : 0); +} + +/* + * Lookup the SA given by the COOKIES and possibly the MESSAGE_ID unless + * a null pointer, meaning we are looking for phase 1 SAs. + */ +struct sa * +sa_lookup(u_int8_t *cookies, u_int8_t *message_id) +{ + u_int16_t bucket = 0; + int i; + struct sa *sa; + u_int8_t *cp; + + /* + * We use the cookies to get bits to use as an index into sa_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 = cookies + i; + /* Doing it this way avoids alignment problems. */ + bucket ^= cp[0] | cp[1] << 8; + } + if (message_id) + for (i = 0; i < ISAKMP_HDR_MESSAGE_ID_LEN; i += 2) { + cp = message_id + i; + /* Doing it this way avoids alignment problems. */ + bucket ^= cp[0] | cp[1] << 8; + } + bucket &= bucket_mask; + for (sa = LIST_FIRST(&sa_tab[bucket]); + sa && (memcmp(cookies, sa->cookies, ISAKMP_HDR_COOKIES_LEN) != 0 + || (message_id && memcmp(message_id, sa->message_id, + ISAKMP_HDR_MESSAGE_ID_LEN) != 0) + || (!message_id && !zero_test(sa->message_id, + ISAKMP_HDR_MESSAGE_ID_LEN))); + sa = LIST_NEXT(sa, link)) + ; + + return sa; +} + +/* Create an SA. */ +int +sa_create(struct exchange *exchange, struct transport *t) +{ + struct sa *sa; + + /* + * We want the SA zeroed for sa_free to be able to find out what fields + * have been filled-in. + */ + sa = calloc(1, sizeof *sa); + if (!sa) { + log_error("sa_create: calloc (1, %lu) failed", + (unsigned long)sizeof *sa); + return -1; + } + sa->transport = t; + if (t) + transport_reference(t); + sa->phase = exchange->phase; + memcpy(sa->cookies, exchange->cookies, ISAKMP_HDR_COOKIES_LEN); + memcpy(sa->message_id, exchange->message_id, + ISAKMP_HDR_MESSAGE_ID_LEN); + sa->doi = exchange->doi; + sa->policy_id = -1; + + if (sa->doi->sa_size) { + /* + * Allocate the DOI-specific structure and initialize it to + * zeroes. + */ + sa->data = calloc(1, sa->doi->sa_size); + if (!sa->data) { + log_error("sa_create: calloc (1, %lu) failed", + (unsigned long)sa->doi->sa_size); + free(sa); + return -1; + } + } + TAILQ_INIT(&sa->protos); + + sa_enter(sa); + TAILQ_INSERT_TAIL(&exchange->sa_list, sa, next); + sa_reference(sa); + + LOG_DBG((LOG_SA, 60, + "sa_create: sa %p phase %d added to exchange %p (%s)", sa, + sa->phase, exchange, + exchange->name ? exchange->name : "")); + return 0; +} + +/* + * Dump the internal state of SA to the report channel, with HEADER + * prepended to each line. + */ +void +sa_dump(int cls, int level, char *header, struct sa *sa) +{ + struct proto *proto; + char spi_header[80]; + int i; + + LOG_DBG((cls, level, "%s: %p %s phase %d doi %d flags 0x%x", header, + sa, sa->name ? sa->name : "", sa->phase, sa->doi->id, + sa->flags)); + LOG_DBG((cls, level, "%s: icookie %08x%08x rcookie %08x%08x", header, + decode_32(sa->cookies), decode_32(sa->cookies + 4), + decode_32(sa->cookies + 8), decode_32(sa->cookies + 12))); + LOG_DBG((cls, level, "%s: msgid %08x refcnt %d", header, + decode_32(sa->message_id), sa->refcnt)); + LOG_DBG((cls, level, "%s: life secs %llu kb %llu", header, sa->seconds, + sa->kilobytes)); + for (proto = TAILQ_FIRST(&sa->protos); proto; + proto = TAILQ_NEXT(proto, link)) { + LOG_DBG((cls, level, "%s: suite %d proto %d", header, + proto->no, proto->proto)); + LOG_DBG((cls, level, + "%s: spi_sz[0] %d spi[0] %p spi_sz[1] %d spi[1] %p", + header, proto->spi_sz[0], proto->spi[0], proto->spi_sz[1], + proto->spi[1])); + LOG_DBG((cls, level, "%s: %s, %s", header, + !sa->doi ? "" : + sa->doi->decode_ids("initiator id: %s, responder id: %s", + sa->id_i, sa->id_i_len, + sa->id_r, sa->id_r_len, 0), + !sa->transport ? "" : + sa->transport->vtbl->decode_ids(sa->transport))); + for (i = 0; i < 2; i++) + if (proto->spi[i]) { + snprintf(spi_header, sizeof spi_header, + "%s: spi[%d]", header, i); + LOG_DBG_BUF((cls, level, spi_header, + proto->spi[i], proto->spi_sz[i])); + } + } +} + +/* + * Display the SA's two SPI values. + */ +static void +report_spi(FILE *fd, const u_int8_t *buf, size_t sz, int spi) +{ +#define SBUFSZ (2 * 32 + 9) + char s[SBUFSZ]; + size_t i, j; + + for (i = j = 0; i < sz;) { + snprintf(s + j, sizeof s - j, "%02x", buf[i++]); + j += 2; + if (i % 4 == 0) { + if (i % 32 == 0) { + s[j] = '\0'; + fprintf(fd, "%s", s); + j = 0; + } else + s[j++] = ' '; + } + } + + if (j) { + s[j] = '\0'; + fprintf(fd, "SPI %d: %s\n", spi, s); + } +} + +/* + * Display the transform names to file. + * Structure is taken from pf_key_v2.c, pf_key_v2_set_spi. + * Transform names are taken from /usr/src/sys/crypto/xform.c. + */ +static void +report_proto(FILE *fd, struct proto *proto) +{ + struct ipsec_proto *iproto = proto->data; + int keylen, hashlen; + + switch (proto->proto) { + case IPSEC_PROTO_IPSEC_ESP: + keylen = ipsec_esp_enckeylength(proto); + hashlen = ipsec_esp_authkeylength(proto); + fprintf(fd, "Transform: IPsec ESP\n"); + fprintf(fd, "Encryption key length: %d\n", keylen); + fprintf(fd, "Authentication key length: %d\n", hashlen); + + fprintf(fd, "Encryption algorithm: "); + switch (proto->id) { + case IPSEC_ESP_DES: + case IPSEC_ESP_DES_IV32: + case IPSEC_ESP_DES_IV64: + fprintf(fd, "DES\n"); + break; + + case IPSEC_ESP_3DES: + fprintf(fd, "3DES\n"); + break; + + case IPSEC_ESP_AES: + fprintf(fd, "AES-128 (CBC)\n"); + break; + + case IPSEC_ESP_AES_128_CTR: + fprintf(fd, "AES-128 (CTR)\n"); + break; + + case IPSEC_ESP_CAST: + fprintf(fd, "Cast-128\n"); + break; + + case IPSEC_ESP_BLOWFISH: + fprintf(fd, "Blowfish\n"); + break; + + default: + fprintf(fd, "unknown (%d)\n", proto->id); + } + + fprintf(fd, "Authentication algorithm: "); + switch (iproto->auth) { + case IPSEC_AUTH_HMAC_MD5: + fprintf(fd, "HMAC-MD5\n"); + break; + + case IPSEC_AUTH_HMAC_SHA: + fprintf(fd, "HMAC-SHA1\n"); + break; + + case IPSEC_AUTH_HMAC_RIPEMD: + fprintf(fd, "HMAC-RIPEMD-160\n"); + break; + + case IPSEC_AUTH_HMAC_SHA2_256: + fprintf(fd, "HMAC-SHA2-256\n"); + break; + + case IPSEC_AUTH_HMAC_SHA2_384: + fprintf(fd, "HMAC-SHA2-384\n"); + break; + + case IPSEC_AUTH_HMAC_SHA2_512: + fprintf(fd, "HMAC-SHA2-512\n"); + break; + + case IPSEC_AUTH_DES_MAC: + case IPSEC_AUTH_KPDK: + /* XXX We should be supporting KPDK */ + fprintf(fd, "unknown (%d)", iproto->auth); + break; + + default: + fprintf(fd, "none\n"); + } + break; + + case IPSEC_PROTO_IPSEC_AH: + hashlen = ipsec_ah_keylength(proto); + fprintf(fd, "Transform: IPsec AH\n"); + fprintf(fd, "Encryption not used.\n"); + fprintf(fd, "Authentication key length: %d\n", hashlen); + + fprintf(fd, "Authentication algorithm: "); + switch (proto->id) { + case IPSEC_AH_MD5: + fprintf(fd, "HMAC-MD5\n"); + break; + + case IPSEC_AH_SHA: + fprintf(fd, "HMAC-SHA1\n"); + break; + + case IPSEC_AH_RIPEMD: + fprintf(fd, "HMAC-RIPEMD-160\n"); + break; + + case IPSEC_AH_SHA2_256: + fprintf(fd, "HMAC-SHA2-256\n"); + break; + + case IPSEC_AH_SHA2_384: + fprintf(fd, "HMAC-SHA2-384\n"); + break; + + case IPSEC_AH_SHA2_512: + fprintf(fd, "HMAC-SHA2-512\n"); + break; + + default: + fprintf(fd, "unknown (%d)", proto->id); + } + break; + + default: + fprintf(fd, "report_proto: invalid proto %d\n", proto->proto); + } +} + +/* Report all the SAs to the report channel. */ +void +sa_report(void) +{ + struct sa *sa; + int i; + + for (i = 0; i <= bucket_mask; i++) + for (sa = LIST_FIRST(&sa_tab[i]); sa; sa = LIST_NEXT(sa, link)) + sa_dump(LOG_REPORT, 0, "sa_report", sa); +} + + +/* + * Print an SA's connection details to file SA_FILE. + */ +static void +sa_dump_all(FILE *fd, struct sa *sa) +{ + struct proto *proto; + int i; + + /* SA name and phase. */ + fprintf(fd, "SA name: %s", sa->name ? sa->name : ""); + fprintf(fd, " (Phase %d)\n", sa->phase); + + /* Source and destination IPs. */ + fprintf(fd, sa->transport == NULL ? "" : + sa->transport->vtbl->decode_ids(sa->transport)); + fprintf(fd, "\n"); + + /* Transform information. */ + for (proto = TAILQ_FIRST(&sa->protos); proto; + proto = TAILQ_NEXT(proto, link)) { + /* SPI values. */ + for (i = 0; i < 2; i++) + if (proto->spi[i]) + report_spi(fd, proto->spi[i], proto->spi_sz[i], + i); + else + fprintf(fd, "SPI %d not defined.", i); + + /* Proto values. */ + report_proto(fd, proto); + + /* SA separator. */ + fprintf(fd, "\n"); + } +} + +/* Report info of all SAs to file 'fd'. */ +void +sa_report_all(FILE *fd) +{ + struct sa *sa; + int i; + + for (i = 0; i <= bucket_mask; i++) + for (sa = LIST_FIRST(&sa_tab[i]); sa; sa = LIST_NEXT(sa, link)) + if (sa->phase == 1) + fprintf(fd, "SA name: none (phase 1)\n\n"); + else + sa_dump_all(fd, sa); +} + +/* Free the protocol structure pointed to by PROTO. */ +void +proto_free(struct proto *proto) +{ + struct proto_attr *pa; + struct sa *sa = proto->sa; + int i; + + for (i = 0; i < 2; i++) + if (proto->spi[i]) { + if (sa->doi->delete_spi) + sa->doi->delete_spi(sa, proto, i); + free(proto->spi[i]); + } + TAILQ_REMOVE(&sa->protos, proto, link); + if (proto->data) { + if (sa->doi && sa->doi->free_proto_data) + sa->doi->free_proto_data(proto->data); + free(proto->data); + } + if (proto->xf_cnt) + while ((pa = TAILQ_FIRST(&proto->xfs)) != NULL) { + if (pa->attrs) + free(pa->attrs); + TAILQ_REMOVE(&proto->xfs, pa, next); + free(pa); + } + + LOG_DBG((LOG_SA, 90, "proto_free: freeing %p", proto)); + free(proto); +} + +/* Release all resources this SA is using. */ +void +sa_free(struct sa *sa) +{ + if (sa->death) { + timer_remove_event(sa->death); + sa->death = 0; + sa->refcnt--; + } + if (sa->soft_death) { + timer_remove_event(sa->soft_death); + sa->soft_death = 0; + sa->refcnt--; + } + sa_remove(sa); +} + +/* Remove the SA from the hash table of live SAs. */ +void +sa_remove(struct sa *sa) +{ + LIST_REMOVE(sa, link); + LOG_DBG((LOG_SA, 70, "sa_remove: SA %p removed from SA list", sa)); + sa_release(sa); +} + +/* Raise the reference count of SA. */ +void +sa_reference(struct sa *sa) +{ + sa->refcnt++; + LOG_DBG((LOG_SA, 80, "sa_reference: SA %p now has %d references", + sa, sa->refcnt)); +} + +/* Release a reference to SA. */ +void +sa_release(struct sa *sa) +{ + struct cert_handler *handler; + struct proto *proto; + + LOG_DBG((LOG_SA, 80, "sa_release: SA %p had %d references", + sa, sa->refcnt)); + + if (--sa->refcnt) + return; + + LOG_DBG((LOG_SA, 60, "sa_release: freeing SA %p", sa)); + + while ((proto = TAILQ_FIRST(&sa->protos)) != 0) + proto_free(proto); + if (sa->data) { + if (sa->doi && sa->doi->free_sa_data) + sa->doi->free_sa_data(sa->data); + free(sa->data); + } + if (sa->id_i) + free(sa->id_i); + if (sa->id_r) + free(sa->id_r); + if (sa->recv_cert) { + handler = cert_get(sa->recv_certtype); + if (handler) + handler->cert_free(sa->recv_cert); + } + if (sa->sent_cert) { + handler = cert_get(sa->sent_certtype); + if (handler) + handler->cert_free(sa->sent_cert); + } + if (sa->recv_key) + key_free(sa->recv_keytype, ISAKMP_KEYTYPE_PUBLIC, + sa->recv_key); + if (sa->keynote_key) + free(sa->keynote_key); /* This is just a string */ +#if defined (USE_POLICY) || defined (USE_KEYNOTE) + if (sa->policy_id != -1) + kn_close(sa->policy_id); +#endif + if (sa->name) + free(sa->name); + if (sa->keystate) + free(sa->keystate); +#if defined (USE_NAT_TRAVERSAL) + if (sa->nat_t_keepalive) + timer_remove_event(sa->nat_t_keepalive); +#endif +#if defined (USE_DPD) + if (sa->dpd_event) + timer_remove_event(sa->dpd_event); +#endif + if (sa->transport) + transport_release(sa->transport); + free(sa); +} + +/* + * Rehash the ISAKMP SA this MSG is negotiating with the responder cookie + * filled in. + */ +void +sa_isakmp_upgrade(struct message *msg) +{ + struct sa *sa = TAILQ_FIRST(&msg->exchange->sa_list); + + sa_remove(sa); + GET_ISAKMP_HDR_RCOOKIE(msg->iov[0].iov_base, + sa->cookies + ISAKMP_HDR_ICOOKIE_LEN); + + /* + * We don't install a transport in the initiator case as we don't know + * what local address will be chosen. Do it now instead. + */ + sa->transport = msg->transport; + transport_reference(sa->transport); + sa_enter(sa); +} + +#define ATTRS_SIZE (IKE_ATTR_BLOCK_SIZE + 1) /* XXX Should be dynamic. */ + +struct attr_validation_state { + u_int8_t *attrp[ATTRS_SIZE]; + u_int8_t checked[ATTRS_SIZE]; + int phase; /* IKE (1) or IPSEC (2) attrs? */ + int mode; /* 0 = 'load', 1 = check */ +}; + +/* Validate an attribute. Return 0 on match. */ +static int +sa_validate_xf_attrs(u_int16_t type, u_int8_t *value, u_int16_t len, + void *arg) +{ + struct attr_validation_state *avs = + (struct attr_validation_state *)arg; + + LOG_DBG((LOG_SA, 95, "sa_validate_xf_attrs: phase %d mode %d type %d " + "len %d", avs->phase, avs->mode, type, len)); + + /* Make sure the phase and type are valid. */ + if (avs->phase == 1) { + if (type < IKE_ATTR_ENCRYPTION_ALGORITHM || + type > IKE_ATTR_BLOCK_SIZE) + return 1; + } else if (avs->phase == 2) { + if (type < IPSEC_ATTR_SA_LIFE_TYPE || + type > IPSEC_ATTR_ECN_TUNNEL) + return 1; + } else + return 1; + + if (avs->mode == 0) { /* Load attrs. */ + avs->attrp[type] = value; + return 0; + } + /* Checking for a missing attribute is an immediate failure. */ + if (!avs->attrp[type]) + return 1; + + /* Match the loaded attribute against this one, mark it as checked. */ + avs->checked[type]++; + return memcmp(avs->attrp[type], value, len); +} + +/* + * This function is used to validate the returned proposal (protection suite) + * we get from the responder against a proposal we sent. Only run as initiator. + * We return 0 if a match is found (in any transform of this proposal), 1 + * otherwise. Also see note in sa_add_transform() below. + */ +static int +sa_validate_proto_xf(struct proto *match, struct payload *xf, int phase) +{ + struct attr_validation_state *avs; + struct proto_attr *pa; + int found = 0; + size_t i; + u_int8_t xf_id; + + if (!match->xf_cnt) + return 0; + + if (match->proto != GET_ISAKMP_PROP_PROTO(xf->context->p)) { + LOG_DBG((LOG_SA, 70, "sa_validate_proto_xf: proto %p (#%d) " + "protocol mismatch", match, match->no)); + return 1; + } + avs = (struct attr_validation_state *)calloc(1, sizeof *avs); + if (!avs) { + log_error("sa_validate_proto_xf: calloc (1, %lu)", + (unsigned long)sizeof *avs); + return 1; + } + avs->phase = phase; + + /* Load the "proposal candidate" attribute set. */ + (void) attribute_map(xf->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH(xf->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + sa_validate_xf_attrs, avs); + xf_id = GET_ISAKMP_TRANSFORM_ID(xf->p); + + /* Check against the transforms we suggested. */ + avs->mode++; + for (pa = TAILQ_FIRST(&match->xfs); pa && !found; + pa = TAILQ_NEXT(pa, next)) { + if (xf_id != GET_ISAKMP_TRANSFORM_ID(pa->attrs)) + continue; + + memset(avs->checked, 0, sizeof avs->checked); + if (attribute_map(pa->attrs + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + pa->len - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + sa_validate_xf_attrs, avs) == 0) + found++; + + LOG_DBG((LOG_SA, 80, "sa_validate_proto_xf: attr_map " + "xf %p proto %p pa %p found %d", xf, match, pa, found)); + + if (!found) + continue; + + /* + * Require all attributes present and checked. XXX perhaps + * not? + */ + for (i = 0; i < sizeof avs->checked; i++) + if (avs->attrp[i] && !avs->checked[i]) + found = 0; + + LOG_DBG((LOG_SA, 80, "sa_validate_proto_xf: req_attr " + "xf %p proto %p pa %p found %d", xf, match, pa, found)); + } + free(avs); + return found ? 0 : 1; +} + +/* + * Register the chosen transform XF into SA. As a side effect set PROTOP + * to point at the corresponding proto structure. INITIATOR is true if we + * are the initiator. + */ +int +sa_add_transform(struct sa *sa, struct payload *xf, int initiator, + struct proto **protop) +{ + struct proto *proto; + struct payload *prop = xf->context; + + *protop = 0; + if (!initiator) { + proto = calloc(1, sizeof *proto); + if (!proto) + log_error("sa_add_transform: calloc (1, %lu) failed", + (unsigned long)sizeof *proto); + } else { + /* + * RFC 2408, section 4.2 states the responder SHOULD use the + * proposal number from the initiator (i.e us), in it's + * selected proposal to make this lookup easier. Most vendors + * follow this. One noted exception is the CiscoPIX (and + * perhaps other Cisco products). + * + * We start by matching on the proposal number, as before. + */ + for (proto = TAILQ_FIRST(&sa->protos); + proto && proto->no != GET_ISAKMP_PROP_NO(prop->p); + proto = TAILQ_NEXT(proto, link)) + ; + /* + * If we did not find a match, search through all proposals + * and xforms. + */ + if (!proto || sa_validate_proto_xf(proto, xf, sa->phase) != 0) + for (proto = TAILQ_FIRST(&sa->protos); + proto && sa_validate_proto_xf(proto, xf, + sa->phase) != 0; + proto = TAILQ_NEXT(proto, link)) + ; + } + if (!proto) + return -1; + *protop = proto; + + /* Allocate DOI-specific part. */ + if (!initiator) { + proto->data = calloc(1, sa->doi->proto_size); + if (!proto->data) { + log_error("sa_add_transform: calloc (1, %lu) failed", + (unsigned long)sa->doi->proto_size); + goto cleanup; + } + } + proto->no = GET_ISAKMP_PROP_NO(prop->p); + proto->proto = GET_ISAKMP_PROP_PROTO(prop->p); + proto->spi_sz[0] = GET_ISAKMP_PROP_SPI_SZ(prop->p); + if (proto->spi_sz[0]) { + proto->spi[0] = malloc(proto->spi_sz[0]); + if (!proto->spi[0]) + goto cleanup; + memcpy(proto->spi[0], prop->p + ISAKMP_PROP_SPI_OFF, + proto->spi_sz[0]); + } + proto->chosen = xf; + proto->sa = sa; + proto->id = GET_ISAKMP_TRANSFORM_ID(xf->p); + if (!initiator) + TAILQ_INSERT_TAIL(&sa->protos, proto, link); + + /* Let the DOI get at proto for initializing its own data. */ + if (sa->doi->proto_init) + sa->doi->proto_init(proto, 0); + + LOG_DBG((LOG_SA, 80, + "sa_add_transform: " + "proto %p no %d proto %d chosen %p sa %p id %d", + proto, proto->no, proto->proto, proto->chosen, proto->sa, + proto->id)); + + return 0; + +cleanup: + if (!initiator) { + if (proto->data) + free(proto->data); + free(proto); + } + *protop = 0; + return -1; +} + +/* Delete an SA. Tell the peer if NOTIFY is set. */ +void +sa_delete(struct sa *sa, int notify) +{ + /* Don't bother notifying of Phase 1 SA deletes. */ + if (sa->phase != 1 && notify) + message_send_delete(sa); + sa_free(sa); +} + + +/* Teardown all SAs. */ +void +sa_teardown_all(void) +{ + int i; + struct sa *sa, *next = 0; + + LOG_DBG((LOG_SA, 70, "sa_teardown_all:")); + /* Get Phase 2 SAs. */ + for (i = 0; i <= bucket_mask; i++) + for (sa = LIST_FIRST(&sa_tab[i]); sa; sa = next) { + next = LIST_NEXT(sa, link); + if (sa->phase == 2) { + /* + * Teardown the phase 2 SAs by name, similar + * to ui_teardown. + */ + LOG_DBG((LOG_SA, 70, + "sa_teardown_all: tearing down SA %s", + sa->name)); + connection_teardown(sa->name); + sa_delete(sa, 1); + } + } +} + +/* + * This function will get called when we are closing in on the death time of SA + */ +static void +sa_soft_expire(void *v_sa) +{ + struct sa *sa = v_sa; + + sa->soft_death = 0; + sa_release(sa); + + if ((sa->flags & (SA_FLAG_STAYALIVE | SA_FLAG_REPLACED)) == + SA_FLAG_STAYALIVE) + exchange_establish(sa->name, 0, 0); + else + /* + * Start to watch the use of this SA, so a renegotiation can + * happen as soon as it is shown to be alive. + */ + sa->flags |= SA_FLAG_FADING; +} + +/* SA has passed its best before date. */ +static void +sa_hard_expire(void *v_sa) +{ + struct sa *sa = v_sa; + + sa->death = 0; + sa_release(sa); + + if ((sa->flags & (SA_FLAG_STAYALIVE | SA_FLAG_REPLACED)) == + SA_FLAG_STAYALIVE) + exchange_establish(sa->name, 0, 0); + + sa_delete(sa, 1); +} + +void +sa_reinit(void) +{ + struct sa *sa; + char *tag; + int i; + + /* For now; only do this if we have the proper tag configured. */ + tag = conf_get_str("General", "Renegotiate-on-HUP"); + if (!tag) + return; + + LOG_DBG((LOG_SA, 30, "sa_reinit: renegotiating active connections")); + + /* + * Get phase 2 SAs. Soft expire those without active exchanges. Do + * not touch a phase 2 SA where the soft expiration is not set, ie. + * the SA is not yet established. + */ + for (i = 0; i <= bucket_mask; i++) + for (sa = LIST_FIRST(&sa_tab[i]); sa; sa = LIST_NEXT(sa, link)) + if (sa->phase == 2) + if (exchange_lookup_by_name(sa->name, + sa->phase) == 0 && sa->soft_death) { + timer_remove_event(sa->soft_death); + sa_soft_expire(sa); + } +} + +/* + * Get an SA attribute's flag value out of textual description. + */ +int +sa_flag(char *attr) +{ + static struct sa_flag_map { + char *name; + int flag; + } sa_flag_map[] = { + { + "active-only", SA_FLAG_ACTIVE_ONLY + }, + + /* + * Below this point are flags that are internal to the + * implementation. + */ + { + "__ondemand", SA_FLAG_ONDEMAND + }, + { + "ikecfg", SA_FLAG_IKECFG + }, + }; + size_t i; + + for (i = 0; i < sizeof sa_flag_map / sizeof sa_flag_map[0]; i++) + if (strcasecmp(attr, sa_flag_map[i].name) == 0) + return sa_flag_map[i].flag; + log_print("sa_flag: attribute \"%s\" unknown", attr); + return 0; +} + +/* Mark SA as replaced. */ +void +sa_mark_replaced(struct sa *sa) +{ + LOG_DBG((LOG_SA, 60, "sa_mark_replaced: SA %p (%s) marked as replaced", + sa, sa->name ? sa->name : "unnamed")); + sa->flags |= SA_FLAG_REPLACED; +} + +/* + * Setup expiration timers for SA. This is used for ISAKMP SAs, but also + * possible to use for application SAs if the application does not deal + * with expirations itself. An example is the Linux FreeS/WAN KLIPS IPsec + * stack. + */ +int +sa_setup_expirations(struct sa *sa) +{ + struct timeval expiration; + u_int64_t seconds = sa->seconds; + + /* + * Set the soft timeout to a random percentage between 85 & 95 of + * the negotiated lifetime to break strictly synchronized + * renegotiations. This works better when the randomization is on the + * order of processing plus network-roundtrip times, or larger. + * I.e. it depends on configuration and negotiated lifetimes. + * It is not good to do the decrease on the hard timeout, because then + * we may drop our SA before our peer. + * XXX Better scheme to come? + */ + if (!sa->soft_death) { + gettimeofday(&expiration, 0); + /* + * XXX This should probably be configuration controlled + * somehow. + */ + seconds = sa->seconds * (850 + sysdep_random() % 100) / 1000; + LOG_DBG((LOG_TIMER, 95, + "sa_setup_expirations: SA %p soft timeout in %llu seconds", + sa, seconds)); + expiration.tv_sec += seconds; + sa->soft_death = timer_add_event("sa_soft_expire", + sa_soft_expire, sa, &expiration); + if (!sa->soft_death) { + /* If we don't give up we might start leaking... */ + sa_delete(sa, 1); + return -1; + } + sa_reference(sa); + } + if (!sa->death) { + gettimeofday(&expiration, 0); + LOG_DBG((LOG_TIMER, 95, + "sa_setup_expirations: SA %p hard timeout in %llu seconds", + sa, sa->seconds)); + expiration.tv_sec += sa->seconds; + sa->death = timer_add_event("sa_hard_expire", sa_hard_expire, + sa, &expiration); + if (!sa->death) { + /* If we don't give up we might start leaking... */ + sa_delete(sa, 1); + return -1; + } + sa_reference(sa); + } + return 0; +} -- cgit v1.2.3