/* $OpenBSD: sa.c,v 1.99 2005/07/22 11:36:43 hshoexer 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 #include #include #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 SA_LEN(dst) == addr->len && memcmp(dst, addr->addr, 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, SA_LEN(src)) && memcmp(dst, arg->dst, 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, "%s", 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--; } if (sa->dpd_event) { timer_remove_event(sa->dpd_event); sa->dpd_event = 0; } 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 (sa->policy_id != -1) kn_close(sa->policy_id); if (sa->name) free(sa->name); if (sa->keystate) free(sa->keystate); if (sa->nat_t_keepalive) timer_remove_event(sa->nat_t_keepalive); if (sa->dpd_event) timer_remove_event(sa->dpd_event); 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; bzero(avs->checked, 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) { if (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 ? sa->name : "")); if (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")); if (sa->dpd_event) { timer_remove_event(sa->dpd_event); sa->dpd_event = 0; } 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 + rand_32() % 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; }