/* $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 #include #include #include #include #include #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 : "", 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; }