diff options
Diffstat (limited to 'keyexchange/isakmpd-20041012/message.c')
-rw-r--r-- | keyexchange/isakmpd-20041012/message.c | 2530 |
1 files changed, 2530 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/message.c b/keyexchange/isakmpd-20041012/message.c new file mode 100644 index 0000000..9259e2d --- /dev/null +++ b/keyexchange/isakmpd-20041012/message.c @@ -0,0 +1,2530 @@ +/* $OpenBSD: message.c,v 1.89 2004/09/17 13:45:02 ho Exp $ */ +/* $EOM: message.c,v 1.156 2000/10/10 12:36:39 provos Exp $ */ + +/* + * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. + * Copyright (c) 1999 Angelos D. Keromytis. All rights reserved. + * Copyright (c) 1999, 2000, 2001, 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> + +#include "sysdep.h" + +#include "attribute.h" +#include "cert.h" +#include "constants.h" +#include "crypto.h" +#include "doi.h" +#ifdef USE_DPD +#include "dpd.h" +#endif +#include "exchange.h" +#include "field.h" +#include "hash.h" +#include "ipsec.h" +#include "ipsec_num.h" +#include "isakmp.h" +#include "log.h" +#include "message.h" +#if defined (USE_NAT_TRAVERSAL) +#include "nat_traversal.h" +#endif +#include "prf.h" +#include "sa.h" +#include "timer.h" +#include "transport.h" +#include "util.h" +#include "virtual.h" + +#ifdef __GNUC__ +#define INLINE __inline +#else +#define INLINE +#endif + +/* A local set datatype, coincidentally fd_set suits our purpose fine. */ +typedef fd_set set; +#define ISSET FD_ISSET +#define SET FD_SET +#define ZERO FD_ZERO + +static int message_check_duplicate(struct message *); +static int message_encrypt(struct message *); +static int message_index_payload(struct message *, struct payload *, + u_int8_t ,u_int8_t *); +static int message_parse_transform(struct message *, struct payload *, + u_int8_t, u_int8_t *); +static u_int16_t message_payload_sz(u_int8_t); +static int message_validate_attribute(struct message *, struct payload *); +static int message_validate_cert(struct message *, struct payload *); +static int message_validate_cert_req(struct message *, struct payload *); +static int message_validate_delete(struct message *, struct payload *); +static int message_validate_hash(struct message *, struct payload *); +static int message_validate_id(struct message *, struct payload *); +static int message_validate_key_exch(struct message *, struct payload *); +static int message_validate_nat_d(struct message *, struct payload *); +static int message_validate_nat_oa(struct message *, struct payload *); +static int message_validate_nonce(struct message *, struct payload *); +static int message_validate_notify(struct message *, struct payload *); +static int message_validate_proposal(struct message *, struct payload *); +static int message_validate_sa(struct message *, struct payload *); +static int message_validate_sig(struct message *, struct payload *); +static int message_validate_transform(struct message *, struct payload *); +static int message_validate_vendor(struct message *, struct payload *); + +static void message_packet_log(struct message *); + +static int (*message_validate_payload[])(struct message *, struct payload *) = +{ + message_validate_sa, message_validate_proposal, + message_validate_transform, message_validate_key_exch, + message_validate_id, message_validate_cert, message_validate_cert_req, + message_validate_hash, message_validate_sig, message_validate_nonce, + message_validate_notify, message_validate_delete, + message_validate_vendor, message_validate_attribute, + message_validate_nat_d, message_validate_nat_oa, + message_validate_nat_d, message_validate_nat_oa +}; + +static struct field *fields[] = { + isakmp_sa_fld, isakmp_prop_fld, isakmp_transform_fld, isakmp_ke_fld, + isakmp_id_fld, isakmp_cert_fld, isakmp_certreq_fld, isakmp_hash_fld, + isakmp_sig_fld, isakmp_nonce_fld, isakmp_notify_fld, isakmp_delete_fld, + isakmp_vendor_fld, isakmp_attribute_fld, isakmp_nat_d_fld, + isakmp_nat_oa_fld, isakmp_nat_d_fld, isakmp_nat_oa_fld +}; + +/* + * These maps are used for indexing the payloads in msg->payloads[i]. + * payload_revmap should be updated if the payloads in isakmp_num.cst change. + * payload_map is populated during startup by message_init(). + */ +static u_int8_t payload_revmap[] = { + ISAKMP_PAYLOAD_NONE, ISAKMP_PAYLOAD_SA, ISAKMP_PAYLOAD_PROPOSAL, + ISAKMP_PAYLOAD_TRANSFORM, ISAKMP_PAYLOAD_KEY_EXCH, ISAKMP_PAYLOAD_ID, + ISAKMP_PAYLOAD_CERT, ISAKMP_PAYLOAD_CERT_REQ, ISAKMP_PAYLOAD_HASH, + ISAKMP_PAYLOAD_SIG, ISAKMP_PAYLOAD_NONCE, ISAKMP_PAYLOAD_NOTIFY, + ISAKMP_PAYLOAD_DELETE, ISAKMP_PAYLOAD_VENDOR, ISAKMP_PAYLOAD_ATTRIBUTE, +#ifdef notyet + ISAKMP_PAYLOAD_SAK, ISAKMP_PAYLOAD_SAT, ISAKMP_PAYLOAD_KD, + ISAKMP_PAYLOAD_SEQ, ISAKMP_PAYLOAD_POP +#endif + ISAKMP_PAYLOAD_NAT_D, ISAKMP_PAYLOAD_NAT_OA, + ISAKMP_PAYLOAD_NAT_D_DRAFT, ISAKMP_PAYLOAD_NAT_OA_DRAFT +}; + +static u_int8_t payload_map[256]; +u_int8_t payload_index_max; + +/* + * Fields used for checking monotonic increasing of proposal and transform + * numbers. + */ +static u_int8_t *last_sa = 0; +static u_int32_t last_prop_no; +static u_int8_t *last_prop = 0; +static u_int32_t last_xf_no; + +/* + * Allocate a message structure bound to transport T, and with a first + * segment buffer sized SZ, copied from BUF if given. + */ +struct message * +message_alloc(struct transport *t, u_int8_t *buf, size_t sz) +{ + struct message *msg; + int i; + + /* + * We use calloc(3) because it zeroes the structure which we rely on in + * message_free when determining what sub-allocations to free. + */ + msg = (struct message *)calloc(1, sizeof *msg); + if (!msg) + return 0; + msg->iov = calloc(1, sizeof *msg->iov); + if (!msg->iov) { + message_free(msg); + return 0; + } + msg->iov[0].iov_len = sz; + msg->iov[0].iov_base = malloc(sz); + if (!msg->iov[0].iov_base) { + message_free(msg); + return 0; + } + msg->iovlen = 1; + if (buf) + memcpy(msg->iov[0].iov_base, buf, sz); + msg->nextp = (u_int8_t *)msg->iov[0].iov_base + + ISAKMP_HDR_NEXT_PAYLOAD_OFF; + msg->transport = t; + transport_reference(t); + msg->payload = (struct payload_head *)calloc(payload_index_max, + sizeof *msg->payload); + if (!msg->payload) { + message_free(msg); + return 0; + } + for (i = 0; i < payload_index_max; i++) + TAILQ_INIT(&msg->payload[i]); + TAILQ_INIT(&msg->post_send); + LOG_DBG((LOG_MESSAGE, 90, "message_alloc: allocated %p", msg)); + return msg; +} + +/* + * Allocate a message suitable for a reply to MSG. Just allocate an empty + * ISAKMP header as the first segment. + */ +struct message * +message_alloc_reply(struct message *msg) +{ + struct message *reply; + + reply = message_alloc(msg->transport, 0, ISAKMP_HDR_SZ); + reply->exchange = msg->exchange; + reply->isakmp_sa = msg->isakmp_sa; + if (msg->isakmp_sa) + sa_reference(msg->isakmp_sa); + return reply; +} + +/* Free up all resources used by the MSG message. */ +void +message_free(struct message *msg) +{ + u_int32_t i; + struct payload *payload, *next; + + LOG_DBG((LOG_MESSAGE, 20, "message_free: freeing %p", msg)); + if (!msg) + return; + if (msg->orig && msg->orig != (u_int8_t *) msg->iov[0].iov_base) + free(msg->orig); + if (msg->iov) { + for (i = 0; i < msg->iovlen; i++) + if (msg->iov[i].iov_base) + free(msg->iov[i].iov_base); + free(msg->iov); + } + if (msg->retrans) + timer_remove_event(msg->retrans); + if (msg->payload) { + for (i = 0; i < payload_index_max; i++) + for (payload = payload_first(msg, i); payload; + payload = next) { + next = TAILQ_NEXT(payload, link); + free(payload); + } + free(msg->payload); + } + while (TAILQ_FIRST(&msg->post_send) != 0) + TAILQ_REMOVE(&msg->post_send, TAILQ_FIRST(&msg->post_send), + link); + + /* If we are on the send queue, remove us from there. */ + if (msg->flags & MSG_IN_TRANSIT) + TAILQ_REMOVE(msg->transport->vtbl->get_queue(msg), msg, link); + + if (msg->transport) + transport_release(msg->transport); + + if (msg->isakmp_sa) + sa_release(msg->isakmp_sa); + + free(msg); +} + +/* + * Generic ISAKMP parser. + * MSG is the ISAKMP message to be parsed. NEXT is the type of the first + * payload to be parsed, and it's pointed to by BUF. ACCEPTED_PAYLOADS + * tells what payloads are accepted and FUNC is a pointer to a function + * to be called for each payload found. Returns the total length of the + * parsed payloads. + */ +static int +message_parse_payloads(struct message *msg, struct payload *p, u_int8_t next, + u_int8_t *buf, set *accepted_payloads, int (*func)(struct message *, + struct payload *, u_int8_t, u_int8_t *)) +{ + u_int8_t payload; + u_int16_t len; + int sz = 0; + + do { + LOG_DBG((LOG_MESSAGE, 50, + "message_parse_payloads: offset %ld payload %s", + (long)(buf - (u_int8_t *) msg->iov[0].iov_base), + constant_name(isakmp_payload_cst, next))); + + /* Does this payload's header fit? */ + if (buf + ISAKMP_GEN_SZ > (u_int8_t *)msg->iov[0].iov_base + + msg->iov[0].iov_len) { + log_print("message_parse_payloads: short message"); + message_drop(msg, + ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, 0, 1, 1); + return -1; + } + /* Ponder on the payload that is at BUF... */ + payload = next; + + /* Look at the next payload's type. */ + next = GET_ISAKMP_GEN_NEXT_PAYLOAD(buf); + if (next >= ISAKMP_PAYLOAD_RESERVED_MIN && + next <= ISAKMP_PAYLOAD_RESERVED_MAX) { + log_print("message_parse_payloads: invalid next " + "payload type %s in payload of type %d", + constant_name(isakmp_payload_cst, next), payload); + message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, + 0, 1, 1); + return -1; + } + /* Reserved fields in ISAKMP messages should be zero. */ + if (GET_ISAKMP_GEN_RESERVED(buf) != 0) { + log_print("message_parse_payloads: reserved field " + "non-zero: %x", GET_ISAKMP_GEN_RESERVED(buf)); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, + 0, 1, 1); + return -1; + } + /* + * Decode and validate the payload length field. + */ + len = GET_ISAKMP_GEN_LENGTH(buf); + + if (message_payload_sz(payload) == 0) { + log_print("message_parse_payloads: unknown minimum " + "payload size for payload type %s", + constant_name(isakmp_payload_cst, payload)); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, + 0, 1, 1); + return -1; + } + if (len < message_payload_sz(payload)) { + log_print("message_parse_payloads: payload too " + "short: %u", len); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, + 0, 1, 1); + return -1; + } + if (buf + len > (u_int8_t *)msg->iov[0].iov_base + + msg->iov[0].iov_len) { + log_print("message_parse_payloads: payload too " + "long: %u", len); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, + 0, 1, 1); + return -1; + } + /* Ignore most private payloads. */ + if (next >= ISAKMP_PAYLOAD_PRIVATE_MIN && + next != ISAKMP_PAYLOAD_NAT_D_DRAFT && + next != ISAKMP_PAYLOAD_NAT_OA_DRAFT) { + LOG_DBG((LOG_MESSAGE, 30, "message_parse_payloads: " + "private next payload type %s in payload of " + "type %d ignored", + constant_name(isakmp_payload_cst, next), payload)); + goto next_payload; + } + /* + * Check if the current payload is one of the accepted ones at + * this stage. + */ + if (!ISSET(payload, accepted_payloads)) { + log_print("message_parse_payloads: payload type %s " + "unexpected", constant_name(isakmp_payload_cst, + payload)); + message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, + 0, 1, 1); + return -1; + } + /* Call the payload handler specified by the caller. */ + if (func(msg, p, payload, buf)) + return -1; + +next_payload: + /* Advance to next payload. */ + buf += len; + sz += len; + } + while (next != ISAKMP_PAYLOAD_NONE); + return sz; +} + +/* + * Parse a proposal payload found in message MSG. PAYLOAD is always + * ISAKMP_PAYLOAD_PROPOSAL and ignored in here. It's needed as the API for + * message_parse_payloads requires it. BUF points to the proposal's + * generic payload header. + */ +static int +message_parse_proposal(struct message *msg, struct payload *p, + u_int8_t payload, u_int8_t *buf) +{ + set payload_set; + + /* Put the proposal into the proposal bucket. */ + message_index_payload(msg, p, payload, buf); + + ZERO(&payload_set); + SET(payload_revmap[ISAKMP_PAYLOAD_TRANSFORM], &payload_set); + if (message_parse_payloads(msg, + payload_last(msg, ISAKMP_PAYLOAD_PROPOSAL), + ISAKMP_PAYLOAD_TRANSFORM, buf + ISAKMP_PROP_SPI_OFF + + GET_ISAKMP_PROP_SPI_SZ(buf), &payload_set, message_parse_transform) + == -1) + return -1; + + return 0; +} + +static int +message_parse_transform(struct message *msg, struct payload *p, + u_int8_t payload, u_int8_t *buf) +{ + /* Put the transform into the transform bucket. */ + message_index_payload(msg, p, payload, buf); + + LOG_DBG((LOG_MESSAGE, 50, "Transform %d's attributes", + GET_ISAKMP_TRANSFORM_NO(buf))); +#ifdef USE_DEBUG + attribute_map(buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH(buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + msg->exchange->doi->debug_attribute, msg); +#endif + + return 0; +} + +/* Check payloads for their required minimum size. */ +static u_int16_t +message_payload_sz(u_int8_t payload) +{ + switch (payload) { + case ISAKMP_PAYLOAD_SA: + return ISAKMP_SA_SZ; + case ISAKMP_PAYLOAD_PROPOSAL: + return ISAKMP_PROP_SZ; + case ISAKMP_PAYLOAD_TRANSFORM: + return ISAKMP_TRANSFORM_SZ; + case ISAKMP_PAYLOAD_KEY_EXCH: + return ISAKMP_KE_SZ; + case ISAKMP_PAYLOAD_ID: + return ISAKMP_ID_SZ; + case ISAKMP_PAYLOAD_CERT: + return ISAKMP_CERT_SZ; + case ISAKMP_PAYLOAD_CERT_REQ: + return ISAKMP_CERTREQ_SZ; + case ISAKMP_PAYLOAD_HASH: + return ISAKMP_HASH_SZ; + case ISAKMP_PAYLOAD_SIG: + return ISAKMP_SIG_SZ; + case ISAKMP_PAYLOAD_NONCE: + return ISAKMP_NONCE_SZ; + case ISAKMP_PAYLOAD_NOTIFY: + return ISAKMP_NOTIFY_SZ; + case ISAKMP_PAYLOAD_DELETE: + return ISAKMP_DELETE_SZ; + case ISAKMP_PAYLOAD_VENDOR: + return ISAKMP_VENDOR_SZ; + case ISAKMP_PAYLOAD_ATTRIBUTE: + return ISAKMP_ATTRIBUTE_SZ; +#if defined (USE_NAT_TRAVERSAL) + case ISAKMP_PAYLOAD_NAT_D: + case ISAKMP_PAYLOAD_NAT_D_DRAFT: + return ISAKMP_NAT_D_SZ; + case ISAKMP_PAYLOAD_NAT_OA: + case ISAKMP_PAYLOAD_NAT_OA_DRAFT: + return ISAKMP_NAT_OA_SZ; +#endif + /* Not yet supported and any other unknown payloads. */ + case ISAKMP_PAYLOAD_SAK: + case ISAKMP_PAYLOAD_SAT: + case ISAKMP_PAYLOAD_KD: + case ISAKMP_PAYLOAD_SEQ: + case ISAKMP_PAYLOAD_POP: + default: + return 0; + } +} + +/* Validate the attribute payload P in message MSG. */ +static int +message_validate_attribute(struct message *msg, struct payload *p) +{ +#ifdef USE_ISAKMP_CFG + /* If we don't have an exchange yet, create one. */ + if (!msg->exchange) { + if (zero_test((u_int8_t *) msg->iov[0].iov_base + + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN)) + msg->exchange = exchange_setup_p1(msg, + IPSEC_DOI_IPSEC); + else + msg->exchange = exchange_setup_p2(msg, + IPSEC_DOI_IPSEC); + if (!msg->exchange) { + log_print("message_validate_attribute: can not " + "create exchange"); + message_free(msg); + return -1; + } + } +#endif + return 0; +} + +/* Validate the certificate payload P in message MSG. */ +static int +message_validate_cert(struct message *msg, struct payload *p) +{ + if (GET_ISAKMP_CERT_ENCODING(p->p) >= ISAKMP_CERTENC_RESERVED_MIN) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1, + 1); + return -1; + } + return 0; +} + +/* Validate the certificate request payload P in message MSG. */ +static int +message_validate_cert_req(struct message *msg, struct payload *p) +{ + struct cert_handler *cert; + size_t len = + GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_CERTREQ_AUTHORITY_OFF; + + if (GET_ISAKMP_CERTREQ_TYPE(p->p) >= ISAKMP_CERTENC_RESERVED_MIN) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1, + 1); + return -1; + } + /* + * Check the certificate types we support and if an acceptable + * authority is included in the payload check if it can be decoded + */ + cert = cert_get(GET_ISAKMP_CERTREQ_TYPE(p->p)); + if (!cert || (len && !cert->certreq_validate(p->p + + ISAKMP_CERTREQ_AUTHORITY_OFF, len))) { + message_drop(msg, ISAKMP_NOTIFY_CERT_TYPE_UNSUPPORTED, 0, 1, + 1); + return -1; + } + return 0; +} + +/* + * Validate the delete payload P in message MSG. As a side-effect, create + * an exchange if we do not have one already. + * + * Note: DELETEs are only accepted as part of an INFORMATIONAL exchange. + * exchange_validate() makes sure a HASH payload is present. Due to the order + * of message validation functions in message_validate_payload[] we can be + * sure that the HASH payload has been successfully validated at this point. + */ +static int +message_validate_delete(struct message *msg, struct payload *p) +{ + u_int8_t proto = GET_ISAKMP_DELETE_PROTO(p->p); + struct doi *doi; + struct sa *sa, *isakmp_sa; + struct sockaddr *dst, *dst_isa; + u_int32_t nspis = GET_ISAKMP_DELETE_NSPIS(p->p); + u_int8_t *spis = (u_int8_t *)p->p + ISAKMP_DELETE_SPI_OFF; + u_int32_t i; + char *addr; + + /* Only accept authenticated DELETEs. */ + if ((msg->flags & MSG_AUTHENTICATED) == 0) { + log_print("message_validate_delete: " + "got unauthenticated DELETE"); + message_free(msg); + return -1; + } + + doi = doi_lookup(GET_ISAKMP_DELETE_DOI(p->p)); + if (!doi) { + log_print("message_validate_delete: DOI not supported"); + message_free(msg); + return -1; + } + /* If we don't have an exchange yet, create one. */ + if (!msg->exchange) { + if (zero_test((u_int8_t *) msg->iov[0].iov_base + + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN)) + msg->exchange = exchange_setup_p1(msg, doi->id); + else + msg->exchange = exchange_setup_p2(msg, doi->id); + if (!msg->exchange) { + log_print("message_validate_delete: can not create " + "exchange"); + message_free(msg); + return -1; + } + } + /* Only accept DELETE as part of an INFORMATIONAL exchange. */ + if (msg->exchange->type != ISAKMP_EXCH_INFO) { + log_print("message_validate_delete: delete in exchange other " + "than INFO: %s", constant_name(isakmp_exch_cst, + msg->exchange->type)); + message_free(msg); + return -1; + } + if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto(proto)) { + log_print("message_validate_delete: protocol not supported"); + message_free(msg); + return -1; + } + /* Validate the SPIs. */ + for (i = 0; i < nspis; i++) { + /* Get ISAKMP SA protecting this message. */ + isakmp_sa = msg->isakmp_sa; + if (!isakmp_sa) { + /* XXX should not happen? */ + log_print("message_validate_delete: invalid spi (no " + "valid ISAKMP SA found)"); + message_free(msg); + return -1; + } + isakmp_sa->transport->vtbl->get_dst(isakmp_sa->transport, + &dst_isa); + + /* Get SA to be deleted. */ + msg->transport->vtbl->get_dst(msg->transport, &dst); + if (proto == ISAKMP_PROTO_ISAKMP) + sa = sa_lookup_isakmp_sa(dst, spis + i + * ISAKMP_HDR_COOKIES_LEN); + else + sa = ipsec_sa_lookup(dst, ((u_int32_t *) spis)[i], + proto); + if (!sa) { + LOG_DBG((LOG_MESSAGE, 50, "message_validate_delete: " + "invalid spi (no valid SA found)")); + message_free(msg); + return -1; + } + sa->transport->vtbl->get_dst(sa->transport, &dst); + + /* Destination addresses must match. */ + if (dst->sa_family != dst_isa->sa_family || + memcmp(sockaddr_addrdata(dst_isa), sockaddr_addrdata(dst), + sockaddr_addrlen(dst))) { + sockaddr2text(dst_isa, &addr, 0); + + log_print("message_validate_delete: invalid spi " + "(illegal delete request from %s)", addr); + free(addr); + message_free(msg); + return -1; + } + } + + return 0; +} + +/* + * Validate the hash payload P in message MSG. + * XXX Currently hash payloads are processed by the particular exchanges, + * except INFORMATIONAL. This should be actually done here. + */ +static int +message_validate_hash(struct message *msg, struct payload *p) +{ + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa; + struct hash *hash; + struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH); + struct prf *prf; + u_int8_t *comp_hash, *rest; + u_int8_t message_id[ISAKMP_HDR_MESSAGE_ID_LEN]; + size_t rest_len; + + /* active exchanges other than INFORMATIONAL validates hash payload. */ + if (msg->exchange && (msg->exchange->type != ISAKMP_EXCH_INFO)) + return 0; + + if (isakmp_sa == NULL) { + log_print("message_validate_hash: invalid hash information"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, + 0, 1, 1); + return -1; + } + isa = isakmp_sa->data; + hash = hash_get(isa->hash); + + if (hash == NULL) { + log_print("message_validate_hash: invalid hash information"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, + 0, 1, 1); + return -1; + } + /* If no SKEYID_a, we can not do anything (should not happen). */ + if (!isa->skeyid_a) { + log_print("message_validate_hash: invalid hash information"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, + 0, 1, 1); + return -1; + } + /* Allocate the prf and start calculating our HASH(1). */ + LOG_DBG_BUF((LOG_MISC, 90, "message_validate_hash: SKEYID_a", + isa->skeyid_a, isa->skeyid_len)); + prf = prf_alloc(isa->prf_type, hash->type, isa->skeyid_a, + isa->skeyid_len); + if (!prf) { + message_free(msg); + return -1; + } + comp_hash = (u_int8_t *)malloc(hash->hashsize); + if (!comp_hash) { + log_error("message_validate_hash: malloc (%lu) failed", + (unsigned long)hash->hashsize); + prf_free(prf); + message_free(msg); + return -1; + } + /* This is not an active exchange. */ + GET_ISAKMP_HDR_MESSAGE_ID(msg->iov[0].iov_base, message_id); + + prf->Init(prf->prfctx); + LOG_DBG_BUF((LOG_MISC, 90, "message_validate_hash: message_id", + message_id, ISAKMP_HDR_MESSAGE_ID_LEN)); + prf->Update(prf->prfctx, message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + rest = hashp->p + GET_ISAKMP_GEN_LENGTH(hashp->p); + rest_len = (GET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base) - (rest - + (u_int8_t *)msg->iov[0].iov_base)); + LOG_DBG_BUF((LOG_MISC, 90, + "message_validate_hash: payloads after HASH(1)", rest, rest_len)); + prf->Update(prf->prfctx, rest, rest_len); + prf->Final(comp_hash, prf->prfctx); + prf_free(prf); + + if (memcmp(hashp->p + ISAKMP_HASH_DATA_OFF, comp_hash, + hash->hashsize)) { + log_print("message_validate_hash: invalid hash value for %s " + "payload", payload_first(msg, ISAKMP_PAYLOAD_DELETE) ? + "DELETE" : "NOTIFY"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, + 0, 1, 1); + free(comp_hash); + return -1; + } + free(comp_hash); + + /* Mark the HASH as handled. */ + hashp->flags |= PL_MARK; + + /* Mark message as authenticated. */ + msg->flags |= MSG_AUTHENTICATED; + + return 0; +} + +/* Validate the identification payload P in message MSG. */ +static int +message_validate_id(struct message *msg, struct payload *p) +{ + struct exchange *exchange = msg->exchange; + size_t len = GET_ISAKMP_GEN_LENGTH(p->p); + + if (!exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_id: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + if (exchange->doi + && exchange->doi->validate_id_information(GET_ISAKMP_ID_TYPE(p->p), + p->p + ISAKMP_ID_DOI_DATA_OFF, p->p + ISAKMP_ID_DATA_OFF, len - + ISAKMP_ID_DATA_OFF, exchange)) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION, 0, 1, + 1); + return -1; + } + return 0; +} + +/* Validate the key exchange payload P in message MSG. */ +static int +message_validate_key_exch(struct message *msg, struct payload *p) +{ + struct exchange *exchange = msg->exchange; + size_t len = GET_ISAKMP_GEN_LENGTH(p->p); + + if (!exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_key_exch: " + "payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + if (exchange->doi && exchange->doi->validate_key_information(p->p + + ISAKMP_KE_DATA_OFF, len - ISAKMP_KE_DATA_OFF)) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_KEY_INFORMATION, + 0, 1, 1); + return -1; + } + return 0; +} + +/* Validate the NAT-D payload P in message MSG. */ +static int +message_validate_nat_d(struct message *msg, struct payload *p) +{ + struct exchange *exchange = msg->exchange; + + if (!exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_nat_d: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + + if (exchange->phase != 1) { + log_print("message_validate_nat_d: " + "NAT-D payload must be in phase 1"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + + /* Mark as handled. */ + p->flags |= PL_MARK; + + return 0; +} + +/* Validate the NAT-OA payload P in message MSG. */ +static int +message_validate_nat_oa(struct message *msg, struct payload *p) +{ + struct exchange *exchange = msg->exchange; + + if (!exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_nat_d: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + +#ifdef notyet /* XXX Probably never, due to patent issues. */ + /* Mark as handled. */ + p->flags |= PL_MARK; +#endif + + return 0; +} + +/* Validate the nonce payload P in message MSG. */ +static int +message_validate_nonce(struct message *msg, struct payload *p) +{ + if (!msg->exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_nonce: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + /* Nonces require no specific validation. */ + return 0; +} + +/* + * Validate the notify payload P in message MSG. As a side-effect, create + * an exchange if we do not have one already. + */ +static int +message_validate_notify(struct message *msg, struct payload *p) +{ + u_int8_t proto = GET_ISAKMP_NOTIFY_PROTO(p->p); + u_int16_t type = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p); + struct doi *doi; + + doi = doi_lookup(GET_ISAKMP_NOTIFY_DOI(p->p)); + if (!doi) { + log_print("message_validate_notify: DOI not supported"); + message_free(msg); + return -1; + } + /* If we don't have an exchange yet, create one. */ + if (!msg->exchange) { + if (zero_test((u_int8_t *) msg->iov[0].iov_base + + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN)) + msg->exchange = exchange_setup_p1(msg, doi->id); + else + msg->exchange = exchange_setup_p2(msg, doi->id); + if (!msg->exchange) { + log_print("message_validate_notify: can not create " + "exchange"); + message_free(msg); + return -1; + } + } + if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto(proto)) { + log_print("message_validate_notify: protocol not supported"); + message_free(msg); + return -1; + } + + /* Validate the SPI. XXX Just ISAKMP for now. */ + if (proto == ISAKMP_PROTO_ISAKMP && + GET_ISAKMP_NOTIFY_SPI_SZ(p->p) == ISAKMP_HDR_COOKIES_LEN && + msg->isakmp_sa && + memcmp(p->p + ISAKMP_NOTIFY_SPI_OFF, msg->isakmp_sa->cookies, + ISAKMP_HDR_COOKIES_LEN) != 0) { + log_print("message_validate_notify: bad cookies"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_SPI, 0, 1, 0); + return -1; + } + + if (type < ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE + || (type >= ISAKMP_NOTIFY_RESERVED_MIN + && type < ISAKMP_NOTIFY_PRIVATE_MIN) + || (type >= ISAKMP_NOTIFY_STATUS_RESERVED1_MIN + && type <= ISAKMP_NOTIFY_STATUS_RESERVED1_MAX) + || (type >= ISAKMP_NOTIFY_STATUS_DOI_MIN + && type <= ISAKMP_NOTIFY_STATUS_DOI_MAX + && doi->validate_notification(type)) + || type >= ISAKMP_NOTIFY_STATUS_RESERVED2_MIN) { + log_print("message_validate_notify: " + "message type not supported"); + message_free(msg); + return -1; + } + + return 0; +} + +/* Validate the proposal payload P in message MSG. */ +static int +message_validate_proposal(struct message *msg, struct payload *p) +{ + u_int8_t proto = GET_ISAKMP_PROP_PROTO(p->p); + u_int8_t *sa = p->context->p; + + if (!msg->exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_proposal: " + "payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + if (proto != ISAKMP_PROTO_ISAKMP + && msg->exchange->doi->validate_proto(proto)) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_PROTOCOL_ID, 0, 1, 1); + return -1; + } + /* Check that we get monotonically increasing proposal IDs per SA. */ + if (sa != last_sa) + last_sa = sa; + else if (GET_ISAKMP_PROP_NO(p->p) < last_prop_no) { + message_drop(msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1); + return -1; + } + last_prop_no = GET_ISAKMP_PROP_NO(p->p); + + /* XXX Validate the SPI, and other syntactic things. */ + + return 0; +} + +/* + * Validate the SA payload P in message MSG. + * Aside from normal validation, note what DOI is in use for other + * validation routines to look at. Also index the proposal payloads + * on the fly. + * XXX This assumes PAYLOAD_SA is always the first payload + * to be validated, which is true for IKE, except for quick mode where + * a PAYLOAD_HASH comes first, but in that specific case it does not matter. + * XXX Make sure the above comment is relevant, isn't SA always checked + * first due to the IANA assigned payload number? + */ +static int +message_validate_sa(struct message *msg, struct payload *p) +{ + set payload_set; + size_t len; + u_int32_t doi_id; + struct exchange *exchange = msg->exchange; + u_int8_t *pkt = msg->iov[0].iov_base; + + doi_id = GET_ISAKMP_SA_DOI(p->p); + if (!doi_lookup(doi_id)) { + log_print("message_validate_sa: DOI not supported"); + message_drop(msg, ISAKMP_NOTIFY_DOI_NOT_SUPPORTED, 0, 1, 1); + return -1; + } + /* + * It's time to figure out what SA this message is about. If it is + * already set, then we are creating a new phase 1 SA. Otherwise, + * lookup the SA using the cookies and the message ID. If we cannot + * find it, and the phase 1 SA is ready, setup a phase 2 SA. + */ + if (!exchange) { + if (zero_test(pkt + ISAKMP_HDR_RCOOKIE_OFF, + ISAKMP_HDR_RCOOKIE_LEN)) + exchange = exchange_setup_p1(msg, doi_id); + else if (msg->isakmp_sa->flags & SA_FLAG_READY) + exchange = exchange_setup_p2(msg, doi_id); + else { + /* XXX What to do here? */ + message_free(msg); + return -1; + } + if (!exchange) { + /* XXX Log? */ + message_free(msg); + return -1; + } + } + msg->exchange = exchange; + + /* + * Create a struct sa for each SA payload handed to us unless we are + * the initiator where we only will count them. + */ + if (exchange->initiator) { + /* XXX Count SA payloads. */ + } else if (sa_create(exchange, msg->transport)) { + /* XXX Remove exchange if we just created it? */ + message_free(msg); + return -1; + } + if (exchange->phase == 1) { + msg->isakmp_sa = TAILQ_FIRST(&exchange->sa_list); + if (msg->isakmp_sa) + sa_reference(msg->isakmp_sa); + } + /* + * Let the DOI validate the situation, at the same time it tells us + * what the length of the situation field is. + */ + if (exchange->doi->validate_situation(p->p + ISAKMP_SA_SIT_OFF, &len, + GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_SA_SIT_OFF)) { + log_print("message_validate_sa: situation not supported"); + message_drop(msg, ISAKMP_NOTIFY_SITUATION_NOT_SUPPORTED, + 0, 1, 1); + return -1; + } + /* + * Reset the fields we base our proposal & transform number checks + * on. + */ + last_sa = last_prop = 0; + last_prop_no = last_xf_no = 0; + + /* Go through the PROPOSAL payloads. */ + ZERO(&payload_set); + SET(payload_revmap[ISAKMP_PAYLOAD_PROPOSAL], &payload_set); + if (message_parse_payloads(msg, p, ISAKMP_PAYLOAD_PROPOSAL, + p->p + ISAKMP_SA_SIT_OFF + len, &payload_set, + message_parse_proposal) == -1) + return -1; + + return 0; +} + +/* Validate the signature payload P in message MSG. */ +static int +message_validate_sig(struct message *msg, struct payload *p) +{ + if (!msg->exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_sig: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + /* XXX Not implemented yet. */ + return 0; +} + +/* Validate the transform payload P in message MSG. */ +static int +message_validate_transform(struct message *msg, struct payload *p) +{ + u_int8_t proto = GET_ISAKMP_PROP_PROTO(p->context->p); + u_int8_t *prop = p->context->p; + + if (!msg->exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_transform: " + "payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + if (msg->exchange->doi + ->validate_transform_id(proto, GET_ISAKMP_TRANSFORM_ID(p->p))) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_TRANSFORM_ID, 0, 1, 1); + return -1; + } + /* Check that the reserved field is zero. */ + if (!zero_test(p->p + ISAKMP_TRANSFORM_RESERVED_OFF, + ISAKMP_TRANSFORM_RESERVED_LEN)) { + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + /* + * Check that we get monotonically increasing transform numbers per + * proposal. + */ + if (prop != last_prop) + last_prop = prop; + else if (GET_ISAKMP_TRANSFORM_NO(p->p) <= last_xf_no) { + message_drop(msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1); + return -1; + } + last_xf_no = GET_ISAKMP_TRANSFORM_NO(p->p); + + /* Validate the attributes. */ + if (attribute_map(p->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + msg->exchange->doi->validate_attribute, msg)) { + message_drop(msg, ISAKMP_NOTIFY_ATTRIBUTES_NOT_SUPPORTED, + 0, 1, 1); + return -1; + } + return 0; +} + +/* Validate the vendor payload P in message MSG. */ +static int +message_validate_vendor(struct message *msg, struct payload *p) +{ + if (!msg->exchange) { + /* We should have an exchange at this point. */ + log_print("message_validate_vendor: payload out of sequence"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + return -1; + } + /* Vendor IDs are only allowed in phase 1. */ + if (msg->exchange->phase != 1) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1); + return -1; + } +#if defined (USE_DPD) + dpd_check_vendor_payload(msg, p); +#endif +#if defined (USE_NAT_TRAVERSAL) + nat_t_check_vendor_payload(msg, p); +#endif + if (!(p->flags & PL_MARK)) + LOG_DBG((LOG_MESSAGE, 40, "message_validate_vendor: " + "vendor ID seen")); + return 0; +} + +/* + * Add an index-record pointing to the payload at BUF in message MSG + * to the PAYLOAD bucket of payloads. This allows us to quickly reference + * payloads by type. Also stash the parent payload P link into the new + * node so we can go from transforms -> payloads -> SAs. + */ +static int +message_index_payload(struct message *msg, struct payload *p, u_int8_t payload, + u_int8_t *buf) +{ + struct payload *payload_node; + + /* Put the payload pointer into the right bucket. */ + payload_node = malloc(sizeof *payload_node); + if (!payload_node) + return -1; + payload_node->p = buf; + payload_node->context = p; + payload_node->flags = 0; + TAILQ_INSERT_TAIL(&msg->payload[payload_map[payload]], payload_node, + link); + return 0; +} + +/* + * Group each payload found in MSG by type for easy reference later. + * While doing this, validate the generic parts of the message structure too. + * NEXT is the 1st payload's type. This routine will also register the + * computed message length (i.e. without padding) in msg->iov[0].iov_len. + */ +static int +message_sort_payloads(struct message *msg, u_int8_t next) +{ + set payload_set; + int i, sz; + + ZERO(&payload_set); + for (i = ISAKMP_PAYLOAD_SA; i < payload_index_max; i++) + if (i != ISAKMP_PAYLOAD_PROPOSAL && i != + ISAKMP_PAYLOAD_TRANSFORM) + SET(payload_revmap[i], &payload_set); + sz = message_parse_payloads(msg, 0, next, + (u_int8_t *)msg->iov[0].iov_base + ISAKMP_HDR_SZ, &payload_set, + message_index_payload); + if (sz == -1) + return -1; + msg->iov[0].iov_len = ISAKMP_HDR_SZ + sz; + SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz); + return 0; +} + +/* Run all the generic payload tests that the drafts specify. */ +static int +message_validate_payloads(struct message *msg) +{ + int i; + struct payload *p; + + for (i = ISAKMP_PAYLOAD_SA; i < payload_index_max; i++) + for (p = payload_first(msg, i); p; p = TAILQ_NEXT(p, link)) { + LOG_DBG((LOG_MESSAGE, 60, "message_validate_payloads: " + "payload %s at %p of message %p", + constant_name(isakmp_payload_cst, i), p->p, msg)); + field_dump_payload(fields[i - ISAKMP_PAYLOAD_SA], + p->p); + if (message_validate_payload[i - ISAKMP_PAYLOAD_SA] + (msg, p)) + return -1; + } + return 0; +} + +/* + * All incoming messages go through here. We do generic validity checks + * and try to find or establish SAs. Last but not least we try to find + * the exchange this message, MSG, is part of, and feed it there. + */ +int +message_recv(struct message *msg) +{ + u_int8_t *buf = msg->iov[0].iov_base; + size_t sz = msg->iov[0].iov_len; + u_int8_t exch_type; + int setup_isakmp_sa, msgid_is_zero; + u_int8_t flags; + struct keystate *ks = 0; + struct proto tmp_proto; + struct sa tmp_sa; + + /* Messages shorter than an ISAKMP header are bad. */ + if (sz < ISAKMP_HDR_SZ || sz != GET_ISAKMP_HDR_LENGTH(buf)) { + log_print("message_recv: bad message length"); + message_drop(msg, ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, + 0, 1, 1); + return -1; + } +#ifdef USE_DEBUG + /* Possibly dump a raw hex image of the message to the log channel. */ + message_dump_raw("message_recv", msg, LOG_MESSAGE); +#endif + + /* + * If the responder cookie is zero, this is a request to setup an + * ISAKMP SA. Otherwise the cookies should refer to an existing + * ISAKMP SA. + * + * XXX This is getting ugly, please reread later to see if it can be + * made nicer. + */ + setup_isakmp_sa = zero_test(buf + ISAKMP_HDR_RCOOKIE_OFF, + ISAKMP_HDR_RCOOKIE_LEN); + if (setup_isakmp_sa) { + /* + * This might be a retransmission of a former ISAKMP SA setup + * message. If so, just drop it. + * XXX Must we really look in both the SA and exchange pools? + */ + if (exchange_lookup_from_icookie(buf + ISAKMP_HDR_ICOOKIE_OFF) + || sa_lookup_from_icookie(buf + ISAKMP_HDR_ICOOKIE_OFF)) { + /* + * XXX Later we should differentiate between + * retransmissions and potential replay attacks. + */ + LOG_DBG((LOG_MESSAGE, 90, + "message_recv: dropping setup for existing SA")); + message_free(msg); + return -1; + } + } else { + msg->isakmp_sa = sa_lookup_by_header(buf, 0); + if (msg->isakmp_sa) + sa_reference(msg->isakmp_sa); + + /* + * If we cannot find an ISAKMP SA out of the cookies, this is + * either a responder's first reply, and we need to upgrade + * our exchange, or it's just plain invalid cookies. + */ + if (!msg->isakmp_sa) { + msg->exchange = exchange_lookup_from_icookie(buf + + ISAKMP_HDR_ICOOKIE_OFF); + if (msg->exchange && msg->exchange->phase == 1 + && zero_test(msg->exchange->cookies + + ISAKMP_HDR_RCOOKIE_OFF, ISAKMP_HDR_RCOOKIE_LEN)) + exchange_upgrade_p1(msg); + else { + log_print("message_recv: invalid cookie(s) " + "%08x%08x %08x%08x", + decode_32(buf + ISAKMP_HDR_ICOOKIE_OFF), + decode_32(buf + ISAKMP_HDR_ICOOKIE_OFF + 4), + decode_32(buf + ISAKMP_HDR_RCOOKIE_OFF), + decode_32(buf + ISAKMP_HDR_RCOOKIE_OFF + 4)); + tmp_proto.sa = &tmp_sa; + tmp_sa.doi = doi_lookup(ISAKMP_DOI_ISAKMP); + tmp_proto.proto = ISAKMP_PROTO_ISAKMP; + tmp_proto.spi_sz[1] = ISAKMP_HDR_COOKIES_LEN; + tmp_proto.spi[1] = + buf + ISAKMP_HDR_COOKIES_OFF; + message_drop(msg, ISAKMP_NOTIFY_INVALID_COOKIE, + &tmp_proto, 1, 1); + return -1; + } +#if 0 + msg->isakmp_sa = sa_lookup_from_icookie(buf + + ISAKMP_HDR_ICOOKIE_OFF); + if (msg->isakmp_sa) + sa_isakmp_upgrade(msg); +#endif + } + msg->exchange = exchange_lookup(buf, 1); + } + + if (message_check_duplicate(msg)) + return -1; + + if (GET_ISAKMP_HDR_NEXT_PAYLOAD(buf) >= ISAKMP_PAYLOAD_RESERVED_MIN) { + log_print("message_recv: invalid payload type %d in ISAKMP " + "header (check passphrases, if applicable and in Phase 1)", + GET_ISAKMP_HDR_NEXT_PAYLOAD(buf)); + message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1); + return -1; + } + /* Validate that the message is of version 1.0. */ + if (ISAKMP_VERSION_MAJOR(GET_ISAKMP_HDR_VERSION(buf)) != 1) { + log_print("message_recv: invalid version major %d", + ISAKMP_VERSION_MAJOR(GET_ISAKMP_HDR_VERSION(buf))); + message_drop(msg, ISAKMP_NOTIFY_INVALID_MAJOR_VERSION, 0, 1, + 1); + return -1; + } + if (ISAKMP_VERSION_MINOR(GET_ISAKMP_HDR_VERSION(buf)) != 0) { + log_print("message_recv: invalid version minor %d", + ISAKMP_VERSION_MINOR(GET_ISAKMP_HDR_VERSION(buf))); + message_drop(msg, ISAKMP_NOTIFY_INVALID_MINOR_VERSION, 0, 1, + 1); + return -1; + } + /* + * Validate the exchange type. If it's a DOI-specified exchange wait + * until after all payloads have been seen for the validation as the + * SA payload might not yet have been parsed, thus the DOI might be + * unknown. + */ + exch_type = GET_ISAKMP_HDR_EXCH_TYPE(buf); + if (exch_type == ISAKMP_EXCH_NONE + || (exch_type >= ISAKMP_EXCH_FUTURE_MIN && + exch_type <= ISAKMP_EXCH_FUTURE_MAX) + || (setup_isakmp_sa && exch_type >= ISAKMP_EXCH_DOI_MIN)) { + log_print("message_recv: invalid exchange type %s", + constant_name(isakmp_exch_cst, exch_type)); + message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1, + 1); + return -1; + } + /* + * Check for unrecognized flags, or the encryption flag when we don't + * have an ISAKMP SA to decrypt with. + */ + flags = GET_ISAKMP_HDR_FLAGS(buf); + if (flags & ~(ISAKMP_FLAGS_ENC | ISAKMP_FLAGS_COMMIT | + ISAKMP_FLAGS_AUTH_ONLY)) { + log_print("message_recv: invalid flags 0x%x", + GET_ISAKMP_HDR_FLAGS(buf)); + message_drop(msg, ISAKMP_NOTIFY_INVALID_FLAGS, 0, 1, 1); + return -1; + } + /* + * If we are about to setup an ISAKMP SA, the message ID must be + * zero. + */ + msgid_is_zero = zero_test(buf + ISAKMP_HDR_MESSAGE_ID_OFF, + ISAKMP_HDR_MESSAGE_ID_LEN); + if (setup_isakmp_sa && !msgid_is_zero) { + log_print("message_recv: invalid message id"); + message_drop(msg, ISAKMP_NOTIFY_INVALID_MESSAGE_ID, 0, 1, 1); + return -1; + } + if (!setup_isakmp_sa && msgid_is_zero) { + /* + * XXX Very likely redundant, look at the else clause of the + * if (setup_isakmp_sa) statement above. + */ + msg->exchange = exchange_lookup(buf, 0); + if (!msg->exchange) { + log_print("message_recv: phase 1 message after " + "ISAKMP SA is ready"); + message_free(msg); + return -1; + } else if (msg->exchange->last_sent) { + LOG_DBG((LOG_MESSAGE, 80, "message_recv: resending " + "last message from phase 1")); + message_send(msg->exchange->last_sent); + } + } + if (flags & ISAKMP_FLAGS_ENC) { + if (!msg->isakmp_sa) { + LOG_DBG((LOG_MISC, 10, "message_recv: no isakmp_sa " + "for encrypted message")); + message_free(msg); + return -1; + } + /* Decrypt rest of message using a DOI-specified IV. */ + ks = msg->isakmp_sa->doi->get_keystate(msg); + if (!ks) { + message_free(msg); + return -1; + } + msg->orig = malloc(sz); + if (!msg->orig) { + message_free(msg); + free(ks); + return -1; + } + memcpy(msg->orig, buf, sz); + crypto_decrypt(ks, buf + ISAKMP_HDR_SZ, sz - ISAKMP_HDR_SZ); + } else + msg->orig = buf; + msg->orig_sz = sz; + + /* IKE packet capture */ + message_packet_log(msg); + + /* + * Check the overall payload structure at the same time as indexing + * them by type. + */ + if (GET_ISAKMP_HDR_NEXT_PAYLOAD(buf) != ISAKMP_PAYLOAD_NONE + && message_sort_payloads(msg, GET_ISAKMP_HDR_NEXT_PAYLOAD(buf))) { + if (ks) + free(ks); + return -1; + } + /* + * Run generic payload tests now. If anything fails these checks, the + * message needs either to be retained for later duplicate checks or + * freed entirely. + * XXX Should SAs and even transports be cleaned up then too? + */ + if (message_validate_payloads(msg)) { + if (ks) + free(ks); + return -1; + } + /* + * If we have not found an exchange by now something is definitely + * wrong. + */ + if (!msg->exchange) { + log_print("message_recv: no exchange"); + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1); + if (ks) + free(ks); + return -1; + } + /* + * Now we can validate DOI-specific exchange types. If we have no SA + * DOI-specific exchange types are definitely wrong. + */ + if (exch_type >= ISAKMP_EXCH_DOI_MIN + && exch_type <= ISAKMP_EXCH_DOI_MAX + && msg->exchange->doi->validate_exchange(exch_type)) { + log_print("message_recv: invalid DOI exchange type %d", + exch_type); + message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1, + 1); + if (ks) + free(ks); + return -1; + } + /* Make sure the IV we used gets saved in the proper SA. */ + if (ks) { + if (!msg->exchange->keystate) { + msg->exchange->keystate = ks; + msg->exchange->crypto = ks->xf; + } else + free(ks); + } + /* Handle the flags. */ + if (flags & ISAKMP_FLAGS_ENC) + msg->exchange->flags |= EXCHANGE_FLAG_ENCRYPT; + if ((msg->exchange->flags & EXCHANGE_FLAG_COMMITTED) == 0 + && (flags & ISAKMP_FLAGS_COMMIT)) + msg->exchange->flags |= EXCHANGE_FLAG_HE_COMMITTED; + + /* + * Except for the 3rd Aggressive Mode message, require encryption + * as soon as we have the keystate for it. + */ + if ((flags & ISAKMP_FLAGS_ENC) == 0 && + (msg->exchange->phase == 2 || + (msg->exchange->keystate && + msg->exchange->type != ISAKMP_EXCH_AGGRESSIVE))) { + log_print("message_recv: cleartext phase %d message", + msg->exchange->phase); + message_drop(msg, ISAKMP_NOTIFY_INVALID_FLAGS, 0, 1, 1); + return -1; + } + + /* OK let the exchange logic do the rest. */ + exchange_run(msg); + + return 0; +} + +void +message_send_expire(struct message *msg) +{ + msg->retrans = 0; + + message_send(msg); +} + +/* Queue up message MSG for transmittal. */ +void +message_send(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct message *m; + struct msg_head *q; + + /* Remove retransmissions on this message */ + if (msg->retrans) { + timer_remove_event(msg->retrans); + msg->retrans = 0; + } + /* IKE packet capture */ + message_packet_log(msg); + + /* + * If the ISAKMP SA has set up encryption, encrypt the message. + * However, in a retransmit, it is already encrypted. + */ + if ((msg->flags & MSG_ENCRYPTED) == 0 + && exchange->flags & EXCHANGE_FLAG_ENCRYPT) { + if (!exchange->keystate) { + exchange->keystate = exchange->doi->get_keystate(msg); + if (!exchange->keystate) + return; + exchange->crypto = exchange->keystate->xf; + exchange->flags |= EXCHANGE_FLAG_ENCRYPT; + } + if (message_encrypt(msg)) { + /* XXX Log. */ + return; + } + } + /* Keep the COMMIT bit on. */ + if (exchange->flags & EXCHANGE_FLAG_COMMITTED) + SET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base, + GET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base) + | ISAKMP_FLAGS_COMMIT); + +#ifdef USE_DEBUG + message_dump_raw("message_send", msg, LOG_MESSAGE); +#endif + msg->flags |= MSG_IN_TRANSIT; + exchange->in_transit = msg; + + /* + * If we get a retransmission of a message before our response + * has left the queue, don't queue it again, as it will result + * in a circular list. + */ + q = msg->transport->vtbl->get_queue(msg); + for (m = TAILQ_FIRST(q); m; m = TAILQ_NEXT(m, link)) + if (m == msg) { + LOG_DBG((LOG_MESSAGE, 60, + "message_send: msg %p already on sendq %p", m, q)); + return; + } + TAILQ_INSERT_TAIL(q, msg, link); +} + +/* + * Setup the ISAKMP message header for message MSG. EXCHANGE is the exchange + * type, FLAGS are the ISAKMP header flags and MSG_ID is message ID + * identifying the exchange. + */ +void +message_setup_header(struct message *msg, u_int8_t exchange, u_int8_t flags, + u_int8_t *msg_id) +{ + u_int8_t *buf = msg->iov[0].iov_base; + + SET_ISAKMP_HDR_ICOOKIE(buf, msg->exchange->cookies); + SET_ISAKMP_HDR_RCOOKIE(buf, msg->exchange->cookies + + ISAKMP_HDR_ICOOKIE_LEN); + SET_ISAKMP_HDR_NEXT_PAYLOAD(buf, ISAKMP_PAYLOAD_NONE); + SET_ISAKMP_HDR_VERSION(buf, ISAKMP_VERSION_MAKE(1, 0)); + SET_ISAKMP_HDR_EXCH_TYPE(buf, exchange); + SET_ISAKMP_HDR_FLAGS(buf, flags); + SET_ISAKMP_HDR_MESSAGE_ID(buf, msg_id); + SET_ISAKMP_HDR_LENGTH(buf, msg->iov[0].iov_len); +} + +/* + * Add the payload of type PAYLOAD in BUF sized SZ to the MSG message. + * The caller thereby is released from the responsibility of freeing BUF, + * unless we return a failure of course. If LINK is set the former + * payload's "next payload" field to PAYLOAD. + * + * XXX We might want to resize the iov array several slots at a time. + */ +int +message_add_payload(struct message *msg, u_int8_t payload, u_int8_t *buf, + size_t sz, int link) +{ + struct iovec *new_iov; + struct payload *payload_node; + + payload_node = calloc(1, sizeof *payload_node); + if (!payload_node) { + log_error("message_add_payload: calloc (1, %lu) failed", + (unsigned long)sizeof *payload_node); + return -1; + } + new_iov = (struct iovec *) realloc(msg->iov, (msg->iovlen + 1) * + sizeof *msg->iov); + if (!new_iov) { + log_error("message_add_payload: realloc (%p, %lu) failed", + msg->iov, (msg->iovlen + 1) * + (unsigned long)sizeof *msg->iov); + free(payload_node); + return -1; + } + msg->iov = new_iov; + new_iov[msg->iovlen].iov_base = buf; + new_iov[msg->iovlen].iov_len = sz; + msg->iovlen++; + if (link) + *msg->nextp = payload; + msg->nextp = buf + ISAKMP_GEN_NEXT_PAYLOAD_OFF; + *msg->nextp = ISAKMP_PAYLOAD_NONE; + SET_ISAKMP_GEN_RESERVED(buf, 0); + SET_ISAKMP_GEN_LENGTH(buf, sz); + SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base, + GET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base) + sz); + + /* + * For the sake of exchange_validate we index the payloads even in + * outgoing messages, however context and flags are uninteresting in + * this situation. + */ + payload_node->p = buf; + TAILQ_INSERT_TAIL(&msg->payload[payload_map[payload]], payload_node, + link); + return 0; +} + +/* XXX Move up when ready. */ +struct info_args { + char discr; + u_int32_t doi; + u_int8_t proto; + u_int16_t spi_sz; + union { + struct { + u_int16_t msg_type; + u_int8_t *spi; + } n; + struct { + u_int16_t nspis; + u_int8_t *spis; + } d; +#if defined (USE_DPD) + struct { + u_int16_t msg_type; + u_int8_t *spi; + u_int32_t seq; + } dpd; +#endif + } u; +}; + +/* + * As a reaction to the incoming message MSG create an informational exchange + * protected by ISAKMP_SA and send a notify payload of type NOTIFY, with + * fields initialized from SA. INCOMING is true if the SPI field should be + * filled with the incoming SPI and false if it is to be filled with the + * outgoing one. + * + * XXX Should we handle sending multiple notify payloads? The draft allows + * it, but do we need it? Furthermore, should we not return a success + * status value? + */ +void +message_send_notification(struct message *msg, struct sa *isakmp_sa, + u_int16_t notify, struct proto *proto, int incoming) +{ + struct info_args args; + struct sa *doi_sa = proto ? proto->sa : isakmp_sa; + + args.discr = 'N'; + args.doi = doi_sa ? doi_sa->doi->id : ISAKMP_DOI_ISAKMP; + args.proto = proto ? proto->proto : ISAKMP_PROTO_ISAKMP; + args.spi_sz = proto ? proto->spi_sz[incoming] : 0; + args.u.n.msg_type = notify; + args.u.n.spi = proto ? proto->spi[incoming] : 0; + if (isakmp_sa && (isakmp_sa->flags & SA_FLAG_READY)) + exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, + 0, 0); + else + exchange_establish_p1(msg->transport, ISAKMP_EXCH_INFO, + msg->exchange ? msg->exchange->doi->id : ISAKMP_DOI_ISAKMP, + 0, &args, 0, 0); +} + +/* Send a DELETE inside an informational exchange for each protocol in SA. */ +void +message_send_delete(struct sa *sa) +{ + struct info_args args; + struct proto *proto; + struct sa *isakmp_sa; + struct sockaddr *dst; + + sa->transport->vtbl->get_dst(sa->transport, &dst); + isakmp_sa = sa_isakmp_lookup_by_peer(dst, sysdep_sa_len(dst)); + if (!isakmp_sa) { + /* + * XXX We ought to setup an ISAKMP SA with our peer here and + * send the DELETE over that one. + */ + return; + } + args.discr = 'D'; + args.doi = sa->doi->id; + args.u.d.nspis = 1; + for (proto = TAILQ_FIRST(&sa->protos); proto; + proto = TAILQ_NEXT(proto, link)) { + args.proto = proto->proto; + args.spi_sz = proto->spi_sz[1]; + args.u.d.spis = proto->spi[1]; + exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, + 0, 0); + } +} + +#if defined (USE_DPD) +void +message_send_dpd_notify(struct sa* isakmp_sa, u_int16_t notify, u_int32_t seq) +{ + struct info_args args; + + args.discr = 'P'; + args.doi = IPSEC_DOI_IPSEC; + args.proto = ISAKMP_PROTO_ISAKMP; + args.spi_sz = ISAKMP_HDR_COOKIES_LEN; + args.u.dpd.msg_type = notify; + args.u.dpd.spi = isakmp_sa->cookies; + args.u.dpd.seq = htonl(seq); + + exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, 0, 0); +} +#endif + +/* Build the informational message into MSG. */ +int +message_send_info(struct message *msg) +{ + u_int8_t *buf; + size_t sz = 0; + struct info_args *args = msg->extra; + u_int8_t payload; + + /* Let the DOI get the first hand on the message. */ + if (msg->exchange->doi->informational_pre_hook) + if (msg->exchange->doi->informational_pre_hook(msg)) + return -1; + + switch (args->discr) { +#if defined (USE_DPD) + case 'P': + sz = sizeof args->u.dpd.seq; + /* FALLTHROUGH */ +#endif + case 'N': + sz += ISAKMP_NOTIFY_SPI_OFF + args->spi_sz; + break; + case 'D': + default: /* Silence gcc */ + sz = ISAKMP_DELETE_SPI_OFF + args->u.d.nspis * args->spi_sz; + break; + } + + buf = calloc(1, sz); + if (!buf) { + log_error("message_send_info: calloc (1, %lu) failed", + (unsigned long)sz); + message_free(msg); + return -1; + } + switch (args->discr) { +#if defined (USE_DPD) + case 'P': + memcpy(buf + ISAKMP_NOTIFY_SPI_OFF + args->spi_sz, + &args->u.dpd.seq, sizeof args->u.dpd.seq); + /* FALLTHROUGH */ +#endif + case 'N': + /* Build the NOTIFY payload. */ + payload = ISAKMP_PAYLOAD_NOTIFY; + SET_ISAKMP_NOTIFY_DOI(buf, args->doi); + SET_ISAKMP_NOTIFY_PROTO(buf, args->proto); + SET_ISAKMP_NOTIFY_SPI_SZ(buf, args->spi_sz); + SET_ISAKMP_NOTIFY_MSG_TYPE(buf, args->u.n.msg_type); + memcpy(buf + ISAKMP_NOTIFY_SPI_OFF, args->u.n.spi, + args->spi_sz); + break; + + case 'D': + default: /* Silence GCC. */ + /* Build the DELETE payload. */ + payload = ISAKMP_PAYLOAD_DELETE; + SET_ISAKMP_DELETE_DOI(buf, args->doi); + SET_ISAKMP_DELETE_PROTO(buf, args->proto); + SET_ISAKMP_DELETE_SPI_SZ(buf, args->spi_sz); + SET_ISAKMP_DELETE_NSPIS(buf, args->u.d.nspis); + memcpy(buf + ISAKMP_DELETE_SPI_OFF, args->u.d.spis, + args->u.d.nspis * args->spi_sz); + msg->flags |= MSG_PRIORITIZED; + break; + } + + if (message_add_payload(msg, payload, buf, sz, 1)) { + free(buf); + message_free(msg); + return -1; + } + /* Let the DOI get the last hand on the message. */ + if (msg->exchange->doi->informational_post_hook) + if (msg->exchange->doi->informational_post_hook(msg)) { + message_free(msg); + return -1; + } + return 0; +} + +/* + * Drop the MSG message due to reason given in NOTIFY. If NOTIFY is set + * send out a notification to the originator. Fill this notification with + * values from PROTO. INCOMING decides which SPI to include. If CLEAN is + * set, free the message when ready with it. + */ +void +message_drop(struct message *msg, int notify, struct proto *proto, + int incoming, int clean) +{ + struct transport *t = msg->transport; + struct sockaddr *dst; + char *address; + short port = 0; + + t->vtbl->get_dst(t, &dst); + if (sockaddr2text(dst, &address, 0)) { + log_error("message_drop: sockaddr2text () failed"); + address = 0; + } + switch (dst->sa_family) { + case AF_INET: + port = ((struct sockaddr_in *)dst)->sin_port; + break; + case AF_INET6: + port = ((struct sockaddr_in6 *)dst)->sin6_port; + break; + default: + log_print("message_drop: unknown protocol family %d", + dst->sa_family); + } + + log_print("dropped message from %s port %d due to notification type " + "%s", address ? address : "<unknown>", htons(port), + constant_name(isakmp_notify_cst, notify)); + + if (address) + free(address); + + /* If specified, return a notification. */ + if (notify) + message_send_notification(msg, msg->isakmp_sa, notify, proto, + incoming); + if (clean) + message_free(msg); +} + +/* + * If the user demands debug printouts, printout MSG with as much detail + * as we can without resorting to per-payload handling. + */ +void +message_dump_raw(char *header, struct message *msg, int class) +{ + u_int32_t i, j, k = 0; + char buf[80], *p = buf; + + LOG_DBG((class, 70, "%s: message %p", header, msg)); + field_dump_payload(isakmp_hdr_fld, msg->iov[0].iov_base); + for (i = 0; i < msg->iovlen; i++) + for (j = 0; j < msg->iov[i].iov_len; j++) { + snprintf(p, sizeof buf - (int) (p - buf), "%02x", + ((u_int8_t *) msg->iov[i].iov_base)[j]); + p += 2; + if (++k % 32 == 0) { + *p = '\0'; + LOG_DBG((class, 70, "%s: %s", header, buf)); + p = buf; + } else if (k % 4 == 0) + *p++ = ' '; + } + *p = '\0'; + if (p != buf) + LOG_DBG((class, 70, "%s: %s", header, buf)); +} + +static void +message_packet_log(struct message *msg) +{ +#if defined (USE_DEBUG) + struct sockaddr *src, *dst; + struct transport *t = msg->transport; + + /* Don't log retransmissions. Redundant for incoming packets... */ + if (msg->xmits > 0) + return; + +#if defined (USE_NAT_TRAVERSAL) + if (msg->exchange && msg->exchange->flags & EXCHANGE_FLAG_NAT_T_ENABLE) + t = ((struct virtual_transport *)msg->transport)->encap; +#endif + + /* Figure out direction. */ + if (msg->exchange && + msg->exchange->initiator ^ (msg->exchange->step % 2)) { + t->vtbl->get_src(t, &src); + t->vtbl->get_dst(t, &dst); + } else { + t->vtbl->get_src(t, &dst); + t->vtbl->get_dst(t, &src); + } + + log_packet_iov(src, dst, msg->iov, msg->iovlen); +#endif /* USE_DEBUG */ +} + +/* + * Encrypt an outgoing message MSG. As outgoing messages are represented + * with an iovec with one segment per payload, we need to coalesce them + * into just une buffer containing all payloads and some padding before + * we encrypt. + */ +static int +message_encrypt(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + size_t i, sz = 0; + u_int8_t *buf; + + /* If no payloads, nothing to do. */ + if (msg->iovlen == 1) + return 0; + + /* + * For encryption we need to put all payloads together in a single + * buffer. This buffer should be padded to the current crypto + * transform's blocksize. + */ + for (i = 1; i < msg->iovlen; i++) + sz += msg->iov[i].iov_len; + sz = ((sz + exchange->crypto->blocksize - 1) / + exchange->crypto->blocksize) * exchange->crypto->blocksize; + buf = realloc(msg->iov[1].iov_base, sz); + if (!buf) { + log_error("message_encrypt: realloc (%p, %lu) failed", + msg->iov[1].iov_base, (unsigned long) sz); + return -1; + } + msg->iov[1].iov_base = buf; + for (i = 2; i < msg->iovlen; i++) { + memcpy(buf + msg->iov[1].iov_len, msg->iov[i].iov_base, + msg->iov[i].iov_len); + msg->iov[1].iov_len += msg->iov[i].iov_len; + free(msg->iov[i].iov_base); + } + + /* Pad with zeroes. */ + memset(buf + msg->iov[1].iov_len, '\0', sz - msg->iov[1].iov_len); + msg->iov[1].iov_len = sz; + msg->iovlen = 2; + + SET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base, + GET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base) | ISAKMP_FLAGS_ENC); + SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz); + crypto_encrypt(exchange->keystate, buf, msg->iov[1].iov_len); + msg->flags |= MSG_ENCRYPTED; + + /* Update the IV so we can decrypt the next incoming message. */ + crypto_update_iv(exchange->keystate); + + return 0; +} + +/* + * Check whether the message MSG is a duplicate of the last one negotiating + * this specific SA. + */ +static int +message_check_duplicate(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + size_t sz = msg->iov[0].iov_len; + u_int8_t *pkt = msg->iov[0].iov_base; + + /* If no SA has been found, we cannot test, thus it's good. */ + if (!exchange) + return 0; + + LOG_DBG((LOG_MESSAGE, 90, "message_check_duplicate: last_received %p", + exchange->last_received)); + if (exchange->last_received) { + LOG_DBG_BUF((LOG_MESSAGE, 95, + "message_check_duplicate: last_received", + exchange->last_received->orig, + exchange->last_received->orig_sz)); + /* Is it a duplicate, lose the new one. */ + if (sz == exchange->last_received->orig_sz + && memcmp(pkt, exchange->last_received->orig, sz) == 0) { + LOG_DBG((LOG_MESSAGE, 80, + "message_check_duplicate: dropping dup")); + + /* + * Retransmit if the previos sent message was the last + * of an exchange, otherwise just wait for the + * ordinary retransmission. + */ + if (exchange->last_sent && (exchange->last_sent->flags + & MSG_LAST)) + message_send(exchange->last_sent); + message_free(msg); + return -1; + } + } + /* + * As this new message is an indication that state is moving forward + * at the peer, remove the retransmit timer on our last message. + */ + if (exchange->last_sent) { + if (exchange->last_sent == exchange->in_transit) { + struct message *m = exchange->in_transit; + TAILQ_REMOVE(m->transport->vtbl->get_queue(m), m, + link); + exchange->in_transit = 0; + } + message_free(exchange->last_sent); + exchange->last_sent = 0; + } + return 0; +} + +/* Helper to message_negotiate_sa. */ +static INLINE struct payload * +step_transform(struct payload *tp, struct payload **propp, + struct payload **sap) +{ + tp = TAILQ_NEXT(tp, link); + if (tp) { + *propp = tp->context; + *sap = (*propp)->context; + } + return tp; +} + +/* + * Pick out the first transforms out of MSG (which should contain at least one + * SA payload) we accept as a full protection suite. + */ +int +message_negotiate_sa(struct message *msg, int (*validate)(struct exchange *, + struct sa *, struct sa *)) +{ + struct payload *tp, *propp, *sap, *next_tp = 0, *next_propp, *next_sap; + struct payload *saved_tp = 0, *saved_propp = 0, *saved_sap = 0; + struct sa *sa; + struct proto *proto; + int suite_ok_so_far = 0; + struct exchange *exchange = msg->exchange; + + /* + * This algorithm is a weird bottom-up thing... mostly due to the + * payload links pointing upwards. + * + * The algorithm goes something like this: + * Foreach transform + * If transform is compatible + * Remember that this protocol can work + * Skip to last transform of this protocol + * If next transform belongs to a new protocol inside the same suite + * If no transform was found for the current protocol + * Forget all earlier transforms for protocols in this suite + * Skip to last transform of this suite + * If next transform belongs to a new suite + * If the current protocol had an OK transform + * Skip to the last transform of this SA + * If the next transform belongs to a new SA + * If no transforms have been chosen + * Issue a NO_PROPOSAL_CHOSEN notification + */ + + sa = TAILQ_FIRST(&exchange->sa_list); + for (tp = payload_first(msg, ISAKMP_PAYLOAD_TRANSFORM); tp; + tp = next_tp) { + propp = tp->context; + sap = propp->context; + sap->flags |= PL_MARK; + next_tp = step_transform(tp, &next_propp, &next_sap); + + /* For each transform, see if it is compatible. */ + if (!attribute_map(tp->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH(tp->p) - + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + exchange->doi->is_attribute_incompatible, msg)) { + LOG_DBG((LOG_NEGOTIATION, 30, "message_negotiate_sa: " + "transform %d proto %d proposal %d ok", + GET_ISAKMP_TRANSFORM_NO(tp->p), + GET_ISAKMP_PROP_PROTO(propp->p), + GET_ISAKMP_PROP_NO(propp->p))); + if (sa_add_transform(sa, tp, exchange->initiator, + &proto)) + goto cleanup; + suite_ok_so_far = 1; + + saved_tp = next_tp; + saved_propp = next_propp; + saved_sap = next_sap; + /* Skip to last transform of this protocol proposal. */ + while ((next_tp = step_transform(tp, &next_propp, + &next_sap)) && next_propp == propp) + tp = next_tp; + } +retry_transform: + /* + * Figure out if we will be looking at a new protocol proposal + * inside the current protection suite. + */ + if (next_tp && propp != next_propp && sap == next_sap + && (GET_ISAKMP_PROP_NO(propp->p) + == GET_ISAKMP_PROP_NO(next_propp->p))) { + if (!suite_ok_so_far) { + LOG_DBG((LOG_NEGOTIATION, 30, + "message_negotiate_sa: proto %d proposal " + "%d failed", + GET_ISAKMP_PROP_PROTO(propp->p), + GET_ISAKMP_PROP_NO(propp->p))); + /* + * Remove potentially succeeded choices from + * the SA. + */ + while (TAILQ_FIRST(&sa->protos)) + TAILQ_REMOVE(&sa->protos, + TAILQ_FIRST(&sa->protos), link); + + /* + * Skip to the last transform of this + * protection suite. + */ + while ((next_tp = step_transform(tp, + &next_propp, &next_sap)) + && (GET_ISAKMP_PROP_NO(next_propp->p) + == GET_ISAKMP_PROP_NO(propp->p)) + && next_sap == sap) + tp = next_tp; + } + suite_ok_so_far = 0; + } + /* + * Figure out if we will be looking at a new protection + * suite. + */ + if (!next_tp + || (propp != next_propp && (GET_ISAKMP_PROP_NO(propp->p) + != GET_ISAKMP_PROP_NO(next_propp->p))) + || sap != next_sap) { + /* + * Check if the suite we just considered was OK, if so + * we check it against the accepted ones. + */ + if (suite_ok_so_far) { + if (!validate || validate(exchange, sa, + msg->isakmp_sa)) { + LOG_DBG((LOG_NEGOTIATION, 30, + "message_negotiate_sa: proposal " + "%d succeeded", + GET_ISAKMP_PROP_NO(propp->p))); + + /* + * Skip to the last transform of this + * SA. + */ + while ((next_tp = step_transform(tp, + &next_propp, &next_sap)) + && next_sap == sap) + tp = next_tp; + } else { + /* Backtrack. */ + LOG_DBG((LOG_NEGOTIATION, 30, + "message_negotiate_sa: proposal " + "%d failed", + GET_ISAKMP_PROP_NO(propp->p))); + next_tp = saved_tp; + next_propp = saved_propp; + next_sap = saved_sap; + suite_ok_so_far = 0; + + /* + * Remove potentially succeeded + * choices from the SA. + */ + while (TAILQ_FIRST(&sa->protos)) + TAILQ_REMOVE(&sa->protos, + TAILQ_FIRST(&sa->protos), + link); + goto retry_transform; + } + } + } + /* Have we walked all the proposals of an SA? */ + if (!next_tp || sap != next_sap) { + if (!suite_ok_so_far) { + /* + * XXX We cannot possibly call this a drop... + * seeing we just turn down one of the offers, + * can we? I suggest renaming message_drop to + * something else. + */ + log_print("message_negotiate_sa: no " + "compatible proposal found"); + message_drop(msg, + ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + } + sa = TAILQ_NEXT(sa, next); + } + } + return 0; + +cleanup: + /* + * Remove potentially succeeded choices from the SA. + * XXX Do we leak struct protos and related data here? + */ + while (TAILQ_FIRST(&sa->protos)) + TAILQ_REMOVE(&sa->protos, TAILQ_FIRST(&sa->protos), link); + return -1; +} + +/* + * Add SA, proposal and transform payload(s) to MSG out of information + * found in the exchange MSG is part of.. + */ +int +message_add_sa_payload(struct message *msg) +{ + struct exchange *exchange = msg->exchange; + u_int8_t *sa_buf, *saved_nextp_sa, *saved_nextp_prop; + size_t sa_len, extra_sa_len; + int i, nprotos = 0; + struct proto *proto; + u_int8_t **transforms = 0, **proposals = 0; + size_t *transform_lens = 0, *proposal_lens = 0; + struct sa *sa; + struct doi *doi = exchange->doi; + u_int8_t *spi = 0; + size_t spi_sz; + + /* + * Generate SA payloads. + */ + for (sa = TAILQ_FIRST(&exchange->sa_list); sa; + sa = TAILQ_NEXT(sa, next)) { + /* Setup a SA payload. */ + sa_len = ISAKMP_SA_SIT_OFF + doi->situation_size(); + extra_sa_len = 0; + sa_buf = malloc(sa_len); + if (!sa_buf) { + log_error("message_add_sa_payload: " + "malloc (%lu) failed", (unsigned long)sa_len); + goto cleanup; + } + SET_ISAKMP_SA_DOI(sa_buf, doi->id); + doi->setup_situation(sa_buf); + + /* Count transforms. */ + nprotos = 0; + for (proto = TAILQ_FIRST(&sa->protos); proto; + proto = TAILQ_NEXT(proto, link)) + nprotos++; + + /* + * Allocate transient transform and proposal payload/size + * vectors. + */ + transforms = calloc(nprotos, sizeof *transforms); + if (!transforms) { + log_error("message_add_sa_payload: calloc (%d, %lu) " + "failed", nprotos, + (unsigned long)sizeof *transforms); + goto cleanup; + } + transform_lens = calloc(nprotos, sizeof *transform_lens); + if (!transform_lens) { + log_error("message_add_sa_payload: calloc (%d, %lu) " + "failed", nprotos, + (unsigned long) sizeof *transform_lens); + goto cleanup; + } + proposals = calloc(nprotos, sizeof *proposals); + if (!proposals) { + log_error("message_add_sa_payload: calloc (%d, %lu) " + "failed", nprotos, + (unsigned long)sizeof *proposals); + goto cleanup; + } + proposal_lens = calloc(nprotos, sizeof *proposal_lens); + if (!proposal_lens) { + log_error("message_add_sa_payload: calloc (%d, %lu) " + "failed", nprotos, + (unsigned long)sizeof *proposal_lens); + goto cleanup; + } + /* Pick out the chosen transforms. */ + for (proto = TAILQ_FIRST(&sa->protos), i = 0; proto; + proto = TAILQ_NEXT(proto, link), i++) { + transform_lens[i] = + GET_ISAKMP_GEN_LENGTH(proto->chosen->p); + transforms[i] = malloc(transform_lens[i]); + if (!transforms[i]) { + log_error("message_add_sa_payload: malloc " + "(%lu) failed", + (unsigned long)transform_lens[i]); + goto cleanup; + } + /* Get incoming SPI from application. */ + if (doi->get_spi) { + spi = doi->get_spi(&spi_sz, + GET_ISAKMP_PROP_PROTO(proto->chosen->context->p), + msg); + if (spi_sz && !spi) + goto cleanup; + proto->spi[1] = spi; + proto->spi_sz[1] = spi_sz; + } else + spi_sz = 0; + + proposal_lens[i] = ISAKMP_PROP_SPI_OFF + spi_sz; + proposals[i] = malloc(proposal_lens[i]); + if (!proposals[i]) { + log_error("message_add_sa_payload: malloc " + "(%lu) failed", + (unsigned long)proposal_lens[i]); + goto cleanup; + } + memcpy(transforms[i], proto->chosen->p, + transform_lens[i]); + memcpy(proposals[i], proto->chosen->context->p, + ISAKMP_PROP_SPI_OFF); + SET_ISAKMP_PROP_NTRANSFORMS(proposals[i], 1); + SET_ISAKMP_PROP_SPI_SZ(proposals[i], spi_sz); + if (spi_sz) + memcpy(proposals[i] + ISAKMP_PROP_SPI_OFF, spi, + spi_sz); + extra_sa_len += proposal_lens[i] + transform_lens[i]; + } + + /* + * Add the payloads. As this is a SA, we need to recompute the + * lengths of the payloads containing others. We also need to + * reset these payload's "next payload type" field. + */ + if (message_add_payload(msg, ISAKMP_PAYLOAD_SA, sa_buf, + sa_len, 1)) + goto cleanup; + SET_ISAKMP_GEN_LENGTH(sa_buf, sa_len + extra_sa_len); + sa_buf = 0; + + saved_nextp_sa = msg->nextp; + for (proto = TAILQ_FIRST(&sa->protos), i = 0; proto; + proto = TAILQ_NEXT(proto, link), i++) { + if (message_add_payload(msg, ISAKMP_PAYLOAD_PROPOSAL, + proposals[i], proposal_lens[i], i > 1)) + goto cleanup; + SET_ISAKMP_GEN_LENGTH(proposals[i], proposal_lens[i] + + transform_lens[i]); + proposals[i] = 0; + + saved_nextp_prop = msg->nextp; + if (message_add_payload(msg, ISAKMP_PAYLOAD_TRANSFORM, + transforms[i], transform_lens[i], 0)) + goto cleanup; + msg->nextp = saved_nextp_prop; + transforms[i] = 0; + } + msg->nextp = saved_nextp_sa; + + /* Free the temporary allocations made above. */ + free(transforms); + free(transform_lens); + free(proposals); + free(proposal_lens); + } + return 0; + +cleanup: + if (sa_buf) + free(sa_buf); + for (i = 0; i < nprotos; i++) { + if (transforms[i]) + free(transforms[i]); + if (proposals[i]) + free(proposals[i]); + } + if (transforms) + free(transforms); + if (transform_lens) + free(transform_lens); + if (proposals) + free(proposals); + if (proposal_lens) + free(proposal_lens); + return -1; +} + +/* + * Return a copy of MSG's constants starting from OFFSET and stash the size + * in SZP. It is the callers responsibility to free this up. + */ +u_int8_t * +message_copy(struct message *msg, size_t offset, size_t *szp) +{ + int skip = 0; + size_t i, sz = 0; + ssize_t start = -1; + u_int8_t *buf, *p; + + /* Calculate size of message and where we want to start to copy. */ + for (i = 1; i < msg->iovlen; i++) { + sz += msg->iov[i].iov_len; + if (sz <= offset) + skip = i; + else if (start < 0) + start = offset - (sz - msg->iov[i].iov_len); + } + + /* Allocate and copy. */ + *szp = sz - offset; + buf = malloc(*szp); + if (!buf) + return 0; + p = buf; + for (i = skip + 1; i < msg->iovlen; i++) { + memcpy(p, (u_int8_t *) msg->iov[i].iov_base + start, + msg->iov[i].iov_len - start); + p += msg->iov[i].iov_len - start; + start = 0; + } + return buf; +} + +/* Register a post-send function POST_SEND with message MSG. */ +int +message_register_post_send(struct message *msg, + void (*post_send)(struct message *)) +{ + struct post_send *node; + + node = malloc(sizeof *node); + if (!node) + return -1; + node->func = post_send; + TAILQ_INSERT_TAIL(&msg->post_send, node, link); + return 0; +} + +/* Run the post-send functions of message MSG. */ +void +message_post_send(struct message *msg) +{ + struct post_send *node; + + while ((node = TAILQ_FIRST(&msg->post_send)) != 0) { + TAILQ_REMOVE(&msg->post_send, node, link); + node->func(msg); + free(node); + } +} + +/* Initialize and populate payload_map[]. */ +void +message_init(void) +{ + u_int8_t i; + + memset(&payload_map, 0, sizeof payload_map); + + payload_index_max = sizeof payload_revmap / sizeof payload_revmap[0]; + for (i = 0; i < payload_index_max; i++) { + payload_map[payload_revmap[i]] = i; + LOG_DBG((LOG_MESSAGE, 95, "message_init: payload_map[%d] = %d", + payload_revmap[i], i)); + } +} + +struct payload * +payload_first(struct message *msg, u_int8_t payload) +{ + if (payload_map[payload]) + return TAILQ_FIRST(&msg->payload[payload_map[payload]]); + else + return 0; +} + +struct payload * +payload_last(struct message *msg, u_int8_t payload) +{ + if (payload_map[payload]) + return TAILQ_LAST(&msg->payload[payload_map[payload]], + payload_head); + else + return 0; +} |