/* $OpenBSD: ipsec.c,v 1.130 2007/09/02 15:19:24 deraadt Exp $ */ /* $EOM: ipsec.c,v 1.143 2000/12/11 23:57:42 niklas Exp $ */ /* * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. * Copyright (c) 2001 Angelos D. Keromytis. All rights reserved. * Copyright (c) 2001 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 #include #include #include "sysdep.h" #include "attribute.h" #include "conf.h" #include "constants.h" #include "crypto.h" #include "dh.h" #include "doi.h" #include "dpd.h" #include "exchange.h" #include "hash.h" #include "ike_aggressive.h" #include "ike_auth.h" #include "ike_main_mode.h" #include "ike_quick_mode.h" #include "ipsec.h" #include "ipsec_doi.h" #include "isakmp.h" #include "isakmp_cfg.h" #include "isakmp_fld.h" #include "isakmp_num.h" #include "log.h" #include "math_group.h" #include "message.h" #include "nat_traversal.h" #include "pf_key_v2.h" #include "prf.h" #include "sa.h" #include "timer.h" #include "transport.h" #include "util.h" #include "x509.h" extern int acquire_only; /* Backwards compatibility. */ #ifndef NI_MAXHOST #define NI_MAXHOST 1025 #endif /* The replay window size used for all IPsec protocols if not overridden. */ #define DEFAULT_REPLAY_WINDOW 16 struct ipsec_decode_arg { struct message *msg; struct sa *sa; struct proto *proto; }; /* These variables hold the contacted peers ADT state. */ struct contact { struct sockaddr *addr; socklen_t len; } *contacts = 0; int contact_cnt = 0, contact_limit = 0; static int addr_cmp(const void *, const void *); static int ipsec_add_contact(struct message *); static int ipsec_contacted(struct message *); static int ipsec_debug_attribute(u_int16_t, u_int8_t *, u_int16_t, void *); static void ipsec_delete_spi(struct sa *, struct proto *, int); static int16_t *ipsec_exchange_script(u_int8_t); static void ipsec_finalize_exchange(struct message *); static void ipsec_free_exchange_data(void *); static void ipsec_free_proto_data(void *); static void ipsec_free_sa_data(void *); static struct keystate *ipsec_get_keystate(struct message *); static u_int8_t *ipsec_get_spi(size_t *, u_int8_t, struct message *); static int ipsec_handle_leftover_payload(struct message *, u_int8_t, struct payload *); static int ipsec_informational_post_hook(struct message *); static int ipsec_informational_pre_hook(struct message *); static int ipsec_initiator(struct message *); static void ipsec_proto_init(struct proto *, char *); static int ipsec_responder(struct message *); static void ipsec_setup_situation(u_int8_t *); static int ipsec_set_network(u_int8_t *, u_int8_t *, struct ipsec_sa *); static size_t ipsec_situation_size(void); static u_int8_t ipsec_spi_size(u_int8_t); static int ipsec_validate_attribute(u_int16_t, u_int8_t *, u_int16_t, void *); static int ipsec_validate_exchange(u_int8_t); static int ipsec_validate_id_information(u_int8_t, u_int8_t *, u_int8_t *, size_t, struct exchange *); static int ipsec_validate_key_information(u_int8_t *, size_t); static int ipsec_validate_notification(u_int16_t); static int ipsec_validate_proto(u_int8_t); static int ipsec_validate_situation(u_int8_t *, size_t *, size_t); static int ipsec_validate_transform_id(u_int8_t, u_int8_t); static int ipsec_sa_tag(struct exchange *, struct sa *, struct sa *); static struct doi ipsec_doi = { {0}, IPSEC_DOI_IPSEC, sizeof(struct ipsec_exch), sizeof(struct ipsec_sa), sizeof(struct ipsec_proto), ipsec_debug_attribute, ipsec_delete_spi, ipsec_exchange_script, ipsec_finalize_exchange, ipsec_free_exchange_data, ipsec_free_proto_data, ipsec_free_sa_data, ipsec_get_keystate, ipsec_get_spi, ipsec_handle_leftover_payload, ipsec_informational_post_hook, ipsec_informational_pre_hook, ipsec_is_attribute_incompatible, ipsec_proto_init, ipsec_setup_situation, ipsec_situation_size, ipsec_spi_size, ipsec_validate_attribute, ipsec_validate_exchange, ipsec_validate_id_information, ipsec_validate_key_information, ipsec_validate_notification, ipsec_validate_proto, ipsec_validate_situation, ipsec_validate_transform_id, ipsec_initiator, ipsec_responder, ipsec_decode_ids }; int16_t script_quick_mode[] = { ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ ISAKMP_PAYLOAD_SA, ISAKMP_PAYLOAD_NONCE, EXCHANGE_SCRIPT_SWITCH, ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ ISAKMP_PAYLOAD_SA, ISAKMP_PAYLOAD_NONCE, EXCHANGE_SCRIPT_SWITCH, ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ EXCHANGE_SCRIPT_END }; int16_t script_new_group_mode[] = { ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ ISAKMP_PAYLOAD_SA, EXCHANGE_SCRIPT_SWITCH, ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ ISAKMP_PAYLOAD_SA, EXCHANGE_SCRIPT_END }; struct dst_spi_proto_arg { struct sockaddr *dst; u_int32_t spi; u_int8_t proto; }; /* * Check if SA matches what we are asking for through V_ARG. It has to * be a finished phase 2 SA. * if "proto" arg is 0, match any proto */ static int ipsec_sa_check(struct sa *sa, void *v_arg) { struct dst_spi_proto_arg *arg = v_arg; struct proto *proto; struct sockaddr *dst, *src; int incoming; if (sa->phase != 2 || !(sa->flags & SA_FLAG_READY)) return 0; sa->transport->vtbl->get_dst(sa->transport, &dst); if (memcmp(sockaddr_addrdata(dst), sockaddr_addrdata(arg->dst), sockaddr_addrlen(dst)) == 0) incoming = 0; else { sa->transport->vtbl->get_src(sa->transport, &src); if (memcmp(sockaddr_addrdata(src), sockaddr_addrdata(arg->dst), sockaddr_addrlen(src)) == 0) incoming = 1; else return 0; } for (proto = TAILQ_FIRST(&sa->protos); proto; proto = TAILQ_NEXT(proto, link)) if ((arg->proto == 0 || proto->proto == arg->proto) && memcmp(proto->spi[incoming], &arg->spi, sizeof arg->spi) == 0) return 1; return 0; } /* Find an SA with a "name" of DST, SPI & PROTO. */ struct sa * ipsec_sa_lookup(struct sockaddr *dst, u_int32_t spi, u_int8_t proto) { struct dst_spi_proto_arg arg; arg.dst = dst; arg.spi = spi; arg.proto = proto; return sa_find(ipsec_sa_check, &arg); } /* * Check if SA matches the flow of another SA in V_ARG. It has to * be a finished non-replaced phase 2 SA. * XXX At some point other selectors will matter here too. */ static int ipsec_sa_check_flow(struct sa *sa, void *v_arg) { struct sa *sa2 = v_arg; struct ipsec_sa *isa = sa->data, *isa2 = sa2->data; if (sa == sa2 || sa->phase != 2 || (sa->flags & (SA_FLAG_READY | SA_FLAG_REPLACED)) != SA_FLAG_READY) return 0; if (isa->tproto != isa2->tproto || isa->sport != isa2->sport || isa->dport != isa2->dport) return 0; return isa->src_net->sa_family == isa2->src_net->sa_family && memcmp(sockaddr_addrdata(isa->src_net), sockaddr_addrdata(isa2->src_net), sockaddr_addrlen(isa->src_net)) == 0 && memcmp(sockaddr_addrdata(isa->src_mask), sockaddr_addrdata(isa2->src_mask), sockaddr_addrlen(isa->src_mask)) == 0 && memcmp(sockaddr_addrdata(isa->dst_net), sockaddr_addrdata(isa2->dst_net), sockaddr_addrlen(isa->dst_net)) == 0 && memcmp(sockaddr_addrdata(isa->dst_mask), sockaddr_addrdata(isa2->dst_mask), sockaddr_addrlen(isa->dst_mask)) == 0; } /* * Construct a PF tag if specified in the configuration. * It is possible to use variables to expand the tag: * $id The string representation of the remote ID * $domain The stripped domain part of the ID (for FQDN and UFQDN) */ static int ipsec_sa_tag(struct exchange *exchange, struct sa *sa, struct sa *isakmp_sa) { char *format, *section; char *id_string = NULL, *domain = NULL; int error = -1; size_t len; sa->tag = NULL; if (exchange->name == NULL || (section = exchange->name) == NULL || (format = conf_get_str(section, "PF-Tag")) == NULL) return (0); /* ignore if not present */ len = PF_TAG_NAME_SIZE; if ((sa->tag = calloc(1, len)) == NULL) { log_error("ipsec_sa_tag: calloc"); goto fail; } if (strlcpy(sa->tag, format, len) >= len) { log_print("ipsec_sa_tag: tag too long"); goto fail; } if (isakmp_sa->initiator) id_string = ipsec_id_string(isakmp_sa->id_r, isakmp_sa->id_r_len); else id_string = ipsec_id_string(isakmp_sa->id_i, isakmp_sa->id_i_len); if (strstr(format, "$id") != NULL) { if (id_string == NULL) { log_print("ipsec_sa_tag: cannot get ID"); goto fail; } if (expand_string(sa->tag, len, "$id", id_string) != 0) { log_print("ipsec_sa_tag: failed to expand tag"); goto fail; } } if (strstr(format, "$domain") != NULL) { if (id_string == NULL) { log_print("ipsec_sa_tag: cannot get ID"); goto fail; } if (strncmp(id_string, "fqdn/", strlen("fqdn/")) == 0) domain = strchr(id_string, '.'); else if (strncmp(id_string, "ufqdn/", strlen("ufqdn/")) == 0) domain = strchr(id_string, '@'); if (domain == NULL || strlen(domain) < 2) { log_print("ipsec_sa_tag: no valid domain in ID %s", id_string); goto fail; } domain++; if (expand_string(sa->tag, len, "$domain", domain) != 0) { log_print("ipsec_sa_tag: failed to expand tag"); goto fail; } } LOG_DBG((LOG_SA, 10, "ipsec_sa_tag: tag_len %ld tag \"%s\"", strlen(sa->tag), sa->tag)); error = 0; fail: free(id_string); if (error != 0) { free(sa->tag); sa->tag = NULL; } return (error); } /* * Do IPsec DOI specific finalizations task for the exchange where MSG was * the final message. */ static void ipsec_finalize_exchange(struct message *msg) { struct sa *isakmp_sa = msg->isakmp_sa; struct ipsec_sa *isa; struct exchange *exchange = msg->exchange; struct ipsec_exch *ie = exchange->data; struct sa *sa = 0, *old_sa; struct proto *proto, *last_proto = 0; char *addr1, *addr2, *mask1, *mask2; switch (exchange->phase) { case 1: switch (exchange->type) { case ISAKMP_EXCH_ID_PROT: case ISAKMP_EXCH_AGGRESSIVE: isa = isakmp_sa->data; isa->hash = ie->hash->type; isa->prf_type = ie->prf_type; isa->skeyid_len = ie->skeyid_len; isa->skeyid_d = ie->skeyid_d; isa->skeyid_a = ie->skeyid_a; /* Prevents early free of SKEYID_*. */ ie->skeyid_a = ie->skeyid_d = 0; /* * If a lifetime was negotiated setup the expiration * timers. */ if (isakmp_sa->seconds) sa_setup_expirations(isakmp_sa); if (isakmp_sa->flags & SA_FLAG_NAT_T_KEEPALIVE) nat_t_setup_keepalive(isakmp_sa); break; } break; case 2: switch (exchange->type) { case IKE_EXCH_QUICK_MODE: /* * Tell the application(s) about the SPIs and key * material. */ for (sa = TAILQ_FIRST(&exchange->sa_list); sa; sa = TAILQ_NEXT(sa, next)) { isa = sa->data; if (exchange->initiator) { /* * Initiator is source, responder is * destination. */ if (ipsec_set_network(ie->id_ci, ie->id_cr, isa)) { log_print( "ipsec_finalize_exchange: " "ipsec_set_network " "failed"); return; } } else { /* * Responder is source, initiator is * destination. */ if (ipsec_set_network(ie->id_cr, ie->id_ci, isa)) { log_print( "ipsec_finalize_exchange: " "ipsec_set_network " "failed"); return; } } if (ipsec_sa_tag(exchange, sa, isakmp_sa) == -1) return; for (proto = TAILQ_FIRST(&sa->protos), last_proto = 0; proto; proto = TAILQ_NEXT(proto, link)) { if (pf_key_v2_set_spi(sa, proto, 0, isakmp_sa) || (last_proto && pf_key_v2_group_spis(sa, last_proto, proto, 0)) || pf_key_v2_set_spi(sa, proto, 1, isakmp_sa) || (last_proto && pf_key_v2_group_spis(sa, last_proto, proto, 1))) /* * XXX Tear down this * exchange. */ return; last_proto = proto; } if (sockaddr2text(isa->src_net, &addr1, 0)) addr1 = 0; if (sockaddr2text(isa->src_mask, &mask1, 0)) mask1 = 0; if (sockaddr2text(isa->dst_net, &addr2, 0)) addr2 = 0; if (sockaddr2text(isa->dst_mask, &mask2, 0)) mask2 = 0; LOG_DBG((LOG_EXCHANGE, 50, "ipsec_finalize_exchange: src %s %s " "dst %s %s tproto %u sport %u dport %u", addr1 ? addr1 : "", mask1 ? mask1 : "", addr2 ? addr2 : "", mask2 ? mask2 : "", isa->tproto, ntohs(isa->sport), ntohs(isa->dport))); free(addr1); free(mask1); free(addr2); free(mask2); /* * If this is not an SA acquired by the * kernel, it needs to have a SPD entry * (a.k.a. flow) set up. */ if (!(sa->flags & SA_FLAG_ONDEMAND || conf_get_str("General", "Acquire-Only") || acquire_only) && pf_key_v2_enable_sa(sa, isakmp_sa)) /* XXX Tear down this exchange. */ return; /* * Mark elder SAs with the same flow * information as replaced. */ while ((old_sa = sa_find(ipsec_sa_check_flow, sa)) != 0) sa_mark_replaced(old_sa); } break; } } } /* Set the client addresses in ISA from SRC_ID and DST_ID. */ static int ipsec_set_network(u_int8_t *src_id, u_int8_t *dst_id, struct ipsec_sa *isa) { int id; /* Set source address/mask. */ id = GET_ISAKMP_ID_TYPE(src_id); switch (id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV4_ADDR_SUBNET: isa->src_net = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); if (!isa->src_net) goto memfail; isa->src_net->sa_family = AF_INET; isa->src_net->sa_len = sizeof(struct sockaddr_in); isa->src_mask = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); if (!isa->src_mask) goto memfail; isa->src_mask->sa_family = AF_INET; isa->src_mask->sa_len = sizeof(struct sockaddr_in); break; case IPSEC_ID_IPV6_ADDR: case IPSEC_ID_IPV6_ADDR_SUBNET: isa->src_net = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in6)); if (!isa->src_net) goto memfail; isa->src_net->sa_family = AF_INET6; isa->src_net->sa_len = sizeof(struct sockaddr_in6); isa->src_mask = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in6)); if (!isa->src_mask) goto memfail; isa->src_mask->sa_family = AF_INET6; isa->src_mask->sa_len = sizeof(struct sockaddr_in6); break; case IPSEC_ID_IPV4_RANGE: case IPSEC_ID_IPV6_RANGE: case IPSEC_ID_DER_ASN1_DN: case IPSEC_ID_DER_ASN1_GN: case IPSEC_ID_KEY_ID: default: log_print("ipsec_set_network: ID type %d (%s) not supported", id, constant_name(ipsec_id_cst, id)); return -1; } /* Net */ memcpy(sockaddr_addrdata(isa->src_net), src_id + ISAKMP_ID_DATA_OFF, sockaddr_addrlen(isa->src_net)); /* Mask */ switch (id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV6_ADDR: memset(sockaddr_addrdata(isa->src_mask), 0xff, sockaddr_addrlen(isa->src_mask)); break; case IPSEC_ID_IPV4_ADDR_SUBNET: case IPSEC_ID_IPV6_ADDR_SUBNET: memcpy(sockaddr_addrdata(isa->src_mask), src_id + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(isa->src_net), sockaddr_addrlen(isa->src_mask)); break; } memcpy(&isa->sport, src_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF, IPSEC_ID_PORT_LEN); /* Set destination address. */ id = GET_ISAKMP_ID_TYPE(dst_id); switch (id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV4_ADDR_SUBNET: isa->dst_net = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); if (!isa->dst_net) goto memfail; isa->dst_net->sa_family = AF_INET; isa->dst_net->sa_len = sizeof(struct sockaddr_in); isa->dst_mask = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); if (!isa->dst_mask) goto memfail; isa->dst_mask->sa_family = AF_INET; isa->dst_mask->sa_len = sizeof(struct sockaddr_in); break; case IPSEC_ID_IPV6_ADDR: case IPSEC_ID_IPV6_ADDR_SUBNET: isa->dst_net = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in6)); if (!isa->dst_net) goto memfail; isa->dst_net->sa_family = AF_INET6; isa->dst_net->sa_len = sizeof(struct sockaddr_in6); isa->dst_mask = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in6)); if (!isa->dst_mask) goto memfail; isa->dst_mask->sa_family = AF_INET6; isa->dst_mask->sa_len = sizeof(struct sockaddr_in6); break; } /* Net */ memcpy(sockaddr_addrdata(isa->dst_net), dst_id + ISAKMP_ID_DATA_OFF, sockaddr_addrlen(isa->dst_net)); /* Mask */ switch (id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV6_ADDR: memset(sockaddr_addrdata(isa->dst_mask), 0xff, sockaddr_addrlen(isa->dst_mask)); break; case IPSEC_ID_IPV4_ADDR_SUBNET: case IPSEC_ID_IPV6_ADDR_SUBNET: memcpy(sockaddr_addrdata(isa->dst_mask), dst_id + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(isa->dst_net), sockaddr_addrlen(isa->dst_mask)); break; } memcpy(&isa->tproto, dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PROTO_OFF, IPSEC_ID_PROTO_LEN); memcpy(&isa->dport, dst_id + ISAKMP_ID_DOI_DATA_OFF + IPSEC_ID_PORT_OFF, IPSEC_ID_PORT_LEN); return 0; memfail: log_error("ipsec_set_network: calloc () failed"); return -1; } /* Free the DOI-specific exchange data pointed to by VIE. */ static void ipsec_free_exchange_data(void *vie) { struct ipsec_exch *ie = vie; struct isakmp_cfg_attr *attr; free(ie->sa_i_b); free(ie->id_ci); free(ie->id_cr); free(ie->g_xi); free(ie->g_xr); free(ie->g_xy); free(ie->skeyid); free(ie->skeyid_d); free(ie->skeyid_a); free(ie->skeyid_e); free(ie->hash_i); free(ie->hash_r); if (ie->group) group_free(ie->group); for (attr = LIST_FIRST(&ie->attrs); attr; attr = LIST_FIRST(&ie->attrs)) { LIST_REMOVE(attr, link); if (attr->length) free(attr->value); free(attr); } } /* Free the DOI-specific SA data pointed to by VISA. */ static void ipsec_free_sa_data(void *visa) { struct ipsec_sa *isa = visa; free(isa->src_net); free(isa->src_mask); free(isa->dst_net); free(isa->dst_mask); free(isa->skeyid_a); free(isa->skeyid_d); } /* Free the DOI-specific protocol data of an SA pointed to by VIPROTO. */ static void ipsec_free_proto_data(void *viproto) { struct ipsec_proto *iproto = viproto; int i; for (i = 0; i < 2; i++) free(iproto->keymat[i]); } /* Return exchange script based on TYPE. */ static int16_t * ipsec_exchange_script(u_int8_t type) { switch (type) { case ISAKMP_EXCH_TRANSACTION: return script_transaction; case IKE_EXCH_QUICK_MODE: return script_quick_mode; case IKE_EXCH_NEW_GROUP_MODE: return script_new_group_mode; } return 0; } /* Initialize this DOI, requires doi_init to already have been called. */ void ipsec_init(void) { doi_register(&ipsec_doi); } /* Given a message MSG, return a suitable IV (or rather keystate). */ static struct keystate * ipsec_get_keystate(struct message *msg) { struct keystate *ks; struct hash *hash; /* If we have already have an IV, use it. */ if (msg->exchange && msg->exchange->keystate) { ks = malloc(sizeof *ks); if (!ks) { log_error("ipsec_get_keystate: malloc (%lu) failed", (unsigned long) sizeof *ks); return 0; } memcpy(ks, msg->exchange->keystate, sizeof *ks); return ks; } /* * For phase 2 when no SA yet is setup we need to hash the IV used by * the ISAKMP SA concatenated with the message ID, and use that as an * IV for further cryptographic operations. */ if (!msg->isakmp_sa->keystate) { log_print("ipsec_get_keystate: no keystate in ISAKMP SA %p", msg->isakmp_sa); return 0; } ks = crypto_clone_keystate(msg->isakmp_sa->keystate); if (!ks) return 0; hash = hash_get(((struct ipsec_sa *)msg->isakmp_sa->data)->hash); hash->Init(hash->ctx); LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: final phase 1 IV", ks->riv, ks->xf->blocksize)); hash->Update(hash->ctx, ks->riv, ks->xf->blocksize); LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: message ID", ((u_int8_t *) msg->iov[0].iov_base) + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN)); hash->Update(hash->ctx, ((u_int8_t *) msg->iov[0].iov_base) + ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN); hash->Final(hash->digest, hash->ctx); crypto_init_iv(ks, hash->digest, ks->xf->blocksize); LOG_DBG_BUF((LOG_CRYPTO, 80, "ipsec_get_keystate: phase 2 IV", hash->digest, ks->xf->blocksize)); return ks; } static void ipsec_setup_situation(u_int8_t *buf) { SET_IPSEC_SIT_SIT(buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY); } static size_t ipsec_situation_size(void) { return IPSEC_SIT_SIT_LEN; } static u_int8_t ipsec_spi_size(u_int8_t proto) { return IPSEC_SPI_SIZE; } static int ipsec_validate_attribute(u_int16_t type, u_int8_t * value, u_int16_t len, void *vmsg) { struct message *msg = vmsg; if (msg->exchange->phase == 1 && (type < IKE_ATTR_ENCRYPTION_ALGORITHM || type > IKE_ATTR_GROUP_ORDER)) return -1; if (msg->exchange->phase == 2 && (type < IPSEC_ATTR_SA_LIFE_TYPE || type > IPSEC_ATTR_ECN_TUNNEL)) return -1; return 0; } static int ipsec_validate_exchange(u_int8_t exch) { return exch != IKE_EXCH_QUICK_MODE && exch != IKE_EXCH_NEW_GROUP_MODE; } static int ipsec_validate_id_information(u_int8_t type, u_int8_t *extra, u_int8_t *buf, size_t sz, struct exchange *exchange) { u_int8_t proto = GET_IPSEC_ID_PROTO(extra); u_int16_t port = GET_IPSEC_ID_PORT(extra); LOG_DBG((LOG_MESSAGE, 40, "ipsec_validate_id_information: proto %d port %d type %d", proto, port, type)); if (type < IPSEC_ID_IPV4_ADDR || type > IPSEC_ID_KEY_ID) return -1; switch (type) { case IPSEC_ID_IPV4_ADDR: LOG_DBG_BUF((LOG_MESSAGE, 40, "ipsec_validate_id_information: IPv4", buf, sizeof(struct in_addr))); break; case IPSEC_ID_IPV6_ADDR: LOG_DBG_BUF((LOG_MESSAGE, 40, "ipsec_validate_id_information: IPv6", buf, sizeof(struct in6_addr))); break; case IPSEC_ID_IPV4_ADDR_SUBNET: LOG_DBG_BUF((LOG_MESSAGE, 40, "ipsec_validate_id_information: IPv4 network/netmask", buf, 2 * sizeof(struct in_addr))); break; case IPSEC_ID_IPV6_ADDR_SUBNET: LOG_DBG_BUF((LOG_MESSAGE, 40, "ipsec_validate_id_information: IPv6 network/netmask", buf, 2 * sizeof(struct in6_addr))); break; default: break; } if (exchange->phase == 1 && (proto != IPPROTO_UDP || port != UDP_DEFAULT_PORT) && (proto != 0 || port != 0)) { /* * XXX SSH's ISAKMP tester fails this test (proto 17 - port * 0). */ #ifdef notyet return -1; #else log_print("ipsec_validate_id_information: dubious ID " "information accepted"); #endif } /* XXX More checks? */ return 0; } static int ipsec_validate_key_information(u_int8_t *buf, size_t sz) { /* XXX Not implemented yet. */ return 0; } static int ipsec_validate_notification(u_int16_t type) { return type < IPSEC_NOTIFY_RESPONDER_LIFETIME || type > IPSEC_NOTIFY_INITIAL_CONTACT ? -1 : 0; } static int ipsec_validate_proto(u_int8_t proto) { return proto < IPSEC_PROTO_IPSEC_AH || proto > IPSEC_PROTO_IPCOMP ? -1 : 0; } static int ipsec_validate_situation(u_int8_t *buf, size_t *sz, size_t len) { if (len < IPSEC_SIT_SIT_OFF + IPSEC_SIT_SIT_LEN) { log_print("ipsec_validate_situation: payload too short: %u", (unsigned int) len); return -1; } /* Currently only "identity only" situations are supported. */ if (GET_IPSEC_SIT_SIT(buf) != IPSEC_SIT_IDENTITY_ONLY) return 1; *sz = IPSEC_SIT_SIT_LEN; return 0; } static int ipsec_validate_transform_id(u_int8_t proto, u_int8_t transform_id) { switch (proto) { /* * As no unexpected protocols can occur, we just tie the * default case to the first case, in order to silence a GCC * warning. */ default: case ISAKMP_PROTO_ISAKMP: return transform_id != IPSEC_TRANSFORM_KEY_IKE; case IPSEC_PROTO_IPSEC_AH: return transform_id < IPSEC_AH_MD5 || transform_id > IPSEC_AH_RIPEMD ? -1 : 0; case IPSEC_PROTO_IPSEC_ESP: return transform_id < IPSEC_ESP_DES_IV64 || (transform_id > IPSEC_ESP_AES_128_CTR && transform_id < IPSEC_ESP_AES_MARS) || transform_id > IPSEC_ESP_AES_TWOFISH ? -1 : 0; case IPSEC_PROTO_IPCOMP: return transform_id < IPSEC_IPCOMP_OUI || transform_id > IPSEC_IPCOMP_V42BIS ? -1 : 0; } } static int ipsec_initiator(struct message *msg) { struct exchange *exchange = msg->exchange; int (**script)(struct message *) = 0; /* Check that the SA is coherent with the IKE rules. */ if (exchange->type != ISAKMP_EXCH_TRANSACTION && ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT && exchange->type != ISAKMP_EXCH_AGGRESSIVE && exchange->type != ISAKMP_EXCH_INFO) || (exchange->phase == 2 && exchange->type != IKE_EXCH_QUICK_MODE && exchange->type != ISAKMP_EXCH_INFO))) { log_print("ipsec_initiator: unsupported exchange type %d " "in phase %d", exchange->type, exchange->phase); return -1; } switch (exchange->type) { case ISAKMP_EXCH_ID_PROT: script = ike_main_mode_initiator; break; case ISAKMP_EXCH_AGGRESSIVE: script = ike_aggressive_initiator; break; case ISAKMP_EXCH_TRANSACTION: script = isakmp_cfg_initiator; break; case ISAKMP_EXCH_INFO: return message_send_info(msg); case IKE_EXCH_QUICK_MODE: script = ike_quick_mode_initiator; break; default: log_print("ipsec_initiator: unsupported exchange type %d", exchange->type); return -1; } /* Run the script code for this step. */ if (script) return script[exchange->step] (msg); return 0; } /* * delete all SA's from addr with the associated proto and SPI's * * spis[] is an array of SPIs of size 16-octet for proto ISAKMP * or 4-octet otherwise. */ static void ipsec_delete_spi_list(struct sockaddr *addr, u_int8_t proto, u_int8_t *spis, int nspis, char *type) { struct sa *sa; int i; for (i = 0; i < nspis; i++) { if (proto == ISAKMP_PROTO_ISAKMP) { u_int8_t *spi = spis + i * ISAKMP_HDR_COOKIES_LEN; /* * This really shouldn't happen in IPSEC DOI * code, but Cisco VPN 3000 sends ISAKMP DELETE's * this way. */ sa = sa_lookup_isakmp_sa(addr, spi); } else { u_int32_t spi = ((u_int32_t *)spis)[i]; sa = ipsec_sa_lookup(addr, spi, proto); } if (sa == NULL) { LOG_DBG((LOG_SA, 30, "ipsec_delete_spi_list: could " "not locate SA (SPI %08x, proto %u)", ((u_int32_t *)spis)[i], proto)); continue; } /* Delete the SA and search for the next */ LOG_DBG((LOG_SA, 30, "ipsec_delete_spi_list: " "%s made us delete SA %p (%d references) for proto %d", type, sa, sa->refcnt, proto)); sa_free(sa); } } static int ipsec_responder(struct message *msg) { struct exchange *exchange = msg->exchange; int (**script)(struct message *) = 0; struct payload *p; u_int16_t type; /* Check that a new exchange is coherent with the IKE rules. */ if (exchange->step == 0 && exchange->type != ISAKMP_EXCH_TRANSACTION && ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT && exchange->type != ISAKMP_EXCH_AGGRESSIVE && exchange->type != ISAKMP_EXCH_INFO) || (exchange->phase == 2 && exchange->type != IKE_EXCH_QUICK_MODE && exchange->type != ISAKMP_EXCH_INFO))) { message_drop(msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 1, 0); return -1; } LOG_DBG((LOG_MISC, 30, "ipsec_responder: phase %d exchange %d step %d", exchange->phase, exchange->type, exchange->step)); switch (exchange->type) { case ISAKMP_EXCH_ID_PROT: script = ike_main_mode_responder; break; case ISAKMP_EXCH_AGGRESSIVE: script = ike_aggressive_responder; break; case ISAKMP_EXCH_TRANSACTION: script = isakmp_cfg_responder; break; case ISAKMP_EXCH_INFO: TAILQ_FOREACH(p, &msg->payload[ISAKMP_PAYLOAD_NOTIFY], link) { type = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p); LOG_DBG((LOG_EXCHANGE, 10, "ipsec_responder: got NOTIFY of type %s", constant_name(isakmp_notify_cst, type))); switch (type) { case IPSEC_NOTIFY_INITIAL_CONTACT: /* Handled by leftover logic. */ break; case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE: case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK: dpd_handle_notify(msg, p); break; default: p->flags |= PL_MARK; break; } } /* * If any DELETEs are in here, let the logic of leftover * payloads deal with them. */ return 0; case IKE_EXCH_QUICK_MODE: script = ike_quick_mode_responder; break; default: message_drop(msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 1, 0); return -1; } /* Run the script code for this step. */ if (script) return script[exchange->step] (msg); /* * XXX So far we don't accept any proposals for exchanges we don't * support. */ if (payload_first(msg, ISAKMP_PAYLOAD_SA)) { message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); return -1; } return 0; } static enum hashes from_ike_hash(u_int16_t hash) { switch (hash) { case IKE_HASH_MD5: return HASH_MD5; case IKE_HASH_SHA: return HASH_SHA1; case IKE_HASH_SHA2_256: return HASH_SHA2_256; case IKE_HASH_SHA2_384: return HASH_SHA2_384; case IKE_HASH_SHA2_512: return HASH_SHA2_512; } return -1; } static enum transform from_ike_crypto(u_int16_t crypto) { /* Coincidentally this is the null operation :-) */ return crypto; } /* * Find out whether the attribute of type TYPE with a LEN length value * pointed to by VALUE is incompatible with what we can handle. * VMSG is a pointer to the current message. */ int ipsec_is_attribute_incompatible(u_int16_t type, u_int8_t *value, u_int16_t len, void *vmsg) { struct message *msg = vmsg; u_int16_t dv = decode_16(value); if (msg->exchange->phase == 1) { switch (type) { case IKE_ATTR_ENCRYPTION_ALGORITHM: return !crypto_get(from_ike_crypto(dv)); case IKE_ATTR_HASH_ALGORITHM: return !hash_get(from_ike_hash(dv)); case IKE_ATTR_AUTHENTICATION_METHOD: return !ike_auth_get(dv); case IKE_ATTR_GROUP_DESCRIPTION: return (dv < IKE_GROUP_DESC_MODP_768 || dv > IKE_GROUP_DESC_MODP_1536) && (dv < IKE_GROUP_DESC_MODP_2048 || dv > IKE_GROUP_DESC_MODP_8192); case IKE_ATTR_GROUP_TYPE: return 1; case IKE_ATTR_GROUP_PRIME: return 1; case IKE_ATTR_GROUP_GENERATOR_1: return 1; case IKE_ATTR_GROUP_GENERATOR_2: return 1; case IKE_ATTR_GROUP_CURVE_A: return 1; case IKE_ATTR_GROUP_CURVE_B: return 1; case IKE_ATTR_LIFE_TYPE: return dv < IKE_DURATION_SECONDS || dv > IKE_DURATION_KILOBYTES; case IKE_ATTR_LIFE_DURATION: return len != 2 && len != 4; case IKE_ATTR_PRF: return 1; case IKE_ATTR_KEY_LENGTH: /* * Our crypto routines only allows key-lengths which * are multiples of an octet. */ return dv % 8 != 0; case IKE_ATTR_FIELD_SIZE: return 1; case IKE_ATTR_GROUP_ORDER: return 1; } } else { switch (type) { case IPSEC_ATTR_SA_LIFE_TYPE: return dv < IPSEC_DURATION_SECONDS || dv > IPSEC_DURATION_KILOBYTES; case IPSEC_ATTR_SA_LIFE_DURATION: return len != 2 && len != 4; case IPSEC_ATTR_GROUP_DESCRIPTION: return (dv < IKE_GROUP_DESC_MODP_768 || dv > IKE_GROUP_DESC_MODP_1536) && (dv < IKE_GROUP_DESC_MODP_2048 || IKE_GROUP_DESC_MODP_8192 < dv); case IPSEC_ATTR_ENCAPSULATION_MODE: return dv != IPSEC_ENCAP_TUNNEL && dv != IPSEC_ENCAP_TRANSPORT && dv != IPSEC_ENCAP_UDP_ENCAP_TUNNEL && dv != IPSEC_ENCAP_UDP_ENCAP_TRANSPORT && dv != IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT && dv != IPSEC_ENCAP_UDP_ENCAP_TRANSPORT_DRAFT; case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: return dv < IPSEC_AUTH_HMAC_MD5 || dv > IPSEC_AUTH_HMAC_RIPEMD; case IPSEC_ATTR_KEY_LENGTH: /* * XXX Blowfish needs '0'. Others appear to disregard * this attr? */ return 0; case IPSEC_ATTR_KEY_ROUNDS: return 1; case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: return 1; case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: return 1; case IPSEC_ATTR_ECN_TUNNEL: return 1; } } /* XXX Silence gcc. */ return 1; } /* * Log the attribute of TYPE with a LEN length value pointed to by VALUE * in human-readable form. VMSG is a pointer to the current message. */ int ipsec_debug_attribute(u_int16_t type, u_int8_t *value, u_int16_t len, void *vmsg) { struct message *msg = vmsg; char val[20]; /* XXX Transient solution. */ if (len == 2) snprintf(val, sizeof val, "%d", decode_16(value)); else if (len == 4) snprintf(val, sizeof val, "%d", decode_32(value)); else snprintf(val, sizeof val, "unrepresentable"); LOG_DBG((LOG_MESSAGE, 50, "Attribute %s value %s", constant_name(msg->exchange->phase == 1 ? ike_attr_cst : ipsec_attr_cst, type), val)); return 0; } /* * Decode the attribute of type TYPE with a LEN length value pointed to by * VALUE. VIDA is a pointer to a context structure where we can find the * current message, SA and protocol. */ int ipsec_decode_attribute(u_int16_t type, u_int8_t *value, u_int16_t len, void *vida) { struct ipsec_decode_arg *ida = vida; struct message *msg = ida->msg; struct sa *sa = ida->sa; struct ipsec_sa *isa = sa->data; struct proto *proto = ida->proto; struct ipsec_proto *iproto = proto->data; struct exchange *exchange = msg->exchange; struct ipsec_exch *ie = exchange->data; static int lifetype = 0; if (exchange->phase == 1) { switch (type) { case IKE_ATTR_ENCRYPTION_ALGORITHM: /* XXX Errors possible? */ exchange->crypto = crypto_get(from_ike_crypto( decode_16(value))); break; case IKE_ATTR_HASH_ALGORITHM: /* XXX Errors possible? */ ie->hash = hash_get(from_ike_hash(decode_16(value))); break; case IKE_ATTR_AUTHENTICATION_METHOD: /* XXX Errors possible? */ ie->ike_auth = ike_auth_get(decode_16(value)); break; case IKE_ATTR_GROUP_DESCRIPTION: isa->group_desc = decode_16(value); break; case IKE_ATTR_GROUP_TYPE: break; case IKE_ATTR_GROUP_PRIME: break; case IKE_ATTR_GROUP_GENERATOR_1: break; case IKE_ATTR_GROUP_GENERATOR_2: break; case IKE_ATTR_GROUP_CURVE_A: break; case IKE_ATTR_GROUP_CURVE_B: break; case IKE_ATTR_LIFE_TYPE: lifetype = decode_16(value); return 0; case IKE_ATTR_LIFE_DURATION: switch (lifetype) { case IKE_DURATION_SECONDS: switch (len) { case 2: sa->seconds = decode_16(value); break; case 4: sa->seconds = decode_32(value); break; default: log_print("ipsec_decode_attribute: " "unreasonable lifetime"); } break; case IKE_DURATION_KILOBYTES: switch (len) { case 2: sa->kilobytes = decode_16(value); break; case 4: sa->kilobytes = decode_32(value); break; default: log_print("ipsec_decode_attribute: " "unreasonable lifetime"); } break; default: log_print("ipsec_decode_attribute: unknown " "lifetime type"); } break; case IKE_ATTR_PRF: break; case IKE_ATTR_KEY_LENGTH: exchange->key_length = decode_16(value) / 8; break; case IKE_ATTR_FIELD_SIZE: break; case IKE_ATTR_GROUP_ORDER: break; } } else { switch (type) { case IPSEC_ATTR_SA_LIFE_TYPE: lifetype = decode_16(value); return 0; case IPSEC_ATTR_SA_LIFE_DURATION: switch (lifetype) { case IPSEC_DURATION_SECONDS: switch (len) { case 2: sa->seconds = decode_16(value); break; case 4: sa->seconds = decode_32(value); break; default: log_print("ipsec_decode_attribute: " "unreasonable lifetime"); } break; case IPSEC_DURATION_KILOBYTES: switch (len) { case 2: sa->kilobytes = decode_16(value); break; case 4: sa->kilobytes = decode_32(value); break; default: log_print("ipsec_decode_attribute: " "unreasonable lifetime"); } break; default: log_print("ipsec_decode_attribute: unknown " "lifetime type"); } break; case IPSEC_ATTR_GROUP_DESCRIPTION: isa->group_desc = decode_16(value); break; case IPSEC_ATTR_ENCAPSULATION_MODE: /* * XXX Multiple protocols must have same * encapsulation mode, no? */ iproto->encap_mode = decode_16(value); break; case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: iproto->auth = decode_16(value); break; case IPSEC_ATTR_KEY_LENGTH: iproto->keylen = decode_16(value); break; case IPSEC_ATTR_KEY_ROUNDS: iproto->keyrounds = decode_16(value); break; case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: break; case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: break; case IPSEC_ATTR_ECN_TUNNEL: break; } } lifetype = 0; return 0; } /* * Walk over the attributes of the transform payload found in BUF, and * fill out the fields of the SA attached to MSG. Also mark the SA as * processed. */ void ipsec_decode_transform(struct message *msg, struct sa *sa, struct proto *proto, u_int8_t *buf) { struct ipsec_exch *ie = msg->exchange->data; struct ipsec_decode_arg ida; LOG_DBG((LOG_MISC, 20, "ipsec_decode_transform: transform %d chosen", GET_ISAKMP_TRANSFORM_NO(buf))); ida.msg = msg; ida.sa = sa; ida.proto = proto; /* The default IKE lifetime is 8 hours. */ if (sa->phase == 1) sa->seconds = 28800; /* Extract the attributes and stuff them into the SA. */ attribute_map(buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF, GET_ISAKMP_GEN_LENGTH(buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, ipsec_decode_attribute, &ida); /* * If no pseudo-random function was negotiated, it's HMAC. * XXX As PRF_HMAC currently is zero, this is a no-op. */ if (!ie->prf_type) ie->prf_type = PRF_HMAC; } /* * Delete the IPsec SA represented by the INCOMING direction in protocol PROTO * of the IKE security association SA. */ static void ipsec_delete_spi(struct sa *sa, struct proto *proto, int incoming) { if (sa->phase == 1) return; /* * If the SA was not replaced and was not one acquired through the * kernel (ACQUIRE message), remove the flow associated with it. * We ignore any errors from the disabling of the flow. */ if (sa->flags & SA_FLAG_READY && !(sa->flags & SA_FLAG_ONDEMAND || sa->flags & SA_FLAG_REPLACED || acquire_only || conf_get_str("General", "Acquire-Only"))) pf_key_v2_disable_sa(sa, incoming); /* XXX Error handling? Is it interesting? */ pf_key_v2_delete_spi(sa, proto, incoming); } /* * Store BUF into the g^x entry of the exchange that message MSG belongs to. * PEER is non-zero when the value is our peer's, and zero when it is ours. */ static int ipsec_g_x(struct message *msg, int peer, u_int8_t *buf) { struct exchange *exchange = msg->exchange; struct ipsec_exch *ie = exchange->data; u_int8_t **g_x; int initiator = exchange->initiator ^ peer; char header[32]; g_x = initiator ? &ie->g_xi : &ie->g_xr; *g_x = malloc(ie->g_x_len); if (!*g_x) { log_error("ipsec_g_x: malloc (%lu) failed", (unsigned long)ie->g_x_len); return -1; } memcpy(*g_x, buf, ie->g_x_len); snprintf(header, sizeof header, "ipsec_g_x: g^x%c", initiator ? 'i' : 'r'); LOG_DBG_BUF((LOG_MISC, 80, header, *g_x, ie->g_x_len)); return 0; } /* Generate our DH value. */ int ipsec_gen_g_x(struct message *msg) { struct exchange *exchange = msg->exchange; struct ipsec_exch *ie = exchange->data; u_int8_t *buf; buf = malloc(ISAKMP_KE_SZ + ie->g_x_len); if (!buf) { log_error("ipsec_gen_g_x: malloc (%lu) failed", ISAKMP_KE_SZ + (unsigned long)ie->g_x_len); return -1; } if (message_add_payload(msg, ISAKMP_PAYLOAD_KEY_EXCH, buf, ISAKMP_KE_SZ + ie->g_x_len, 1)) { free(buf); return -1; } if (dh_create_exchange(ie->group, buf + ISAKMP_KE_DATA_OFF)) { log_print("ipsec_gen_g_x: dh_create_exchange failed"); free(buf); return -1; } return ipsec_g_x(msg, 0, buf + ISAKMP_KE_DATA_OFF); } /* Save the peer's DH value. */ int ipsec_save_g_x(struct message *msg) { struct exchange *exchange = msg->exchange; struct ipsec_exch *ie = exchange->data; struct payload *kep; kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH); kep->flags |= PL_MARK; ie->g_x_len = GET_ISAKMP_GEN_LENGTH(kep->p) - ISAKMP_KE_DATA_OFF; /* Check that the given length matches the group's expectancy. */ if (ie->g_x_len != (size_t) dh_getlen(ie->group)) { /* XXX Is this a good notify type? */ message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); return -1; } return ipsec_g_x(msg, 1, kep->p + ISAKMP_KE_DATA_OFF); } /* * Get a SPI for PROTO and the transport MSG passed over. Store the * size where SZ points. NB! A zero return is OK if *SZ is zero. */ static u_int8_t * ipsec_get_spi(size_t *sz, u_int8_t proto, struct message *msg) { struct sockaddr *dst, *src; struct transport *transport = msg->transport; if (msg->exchange->phase == 1) { *sz = 0; return 0; } else { /* We are the destination in the SA we want a SPI for. */ transport->vtbl->get_src(transport, &dst); /* The peer is the source. */ transport->vtbl->get_dst(transport, &src); return pf_key_v2_get_spi(sz, proto, src, dst, msg->exchange->seq); } } /* * We have gotten a payload PAYLOAD of type TYPE, which did not get handled * by the logic of the exchange MSG takes part in. Now is the time to deal * with such a payload if we know how to, if we don't, return -1, otherwise * 0. */ int ipsec_handle_leftover_payload(struct message *msg, u_int8_t type, struct payload *payload) { u_int32_t spisz, nspis; struct sockaddr *dst; int reenter = 0; u_int8_t *spis, proto; struct sa *sa; switch (type) { case ISAKMP_PAYLOAD_DELETE: proto = GET_ISAKMP_DELETE_PROTO(payload->p); nspis = GET_ISAKMP_DELETE_NSPIS(payload->p); spisz = GET_ISAKMP_DELETE_SPI_SZ(payload->p); if (nspis == 0) { LOG_DBG((LOG_SA, 60, "ipsec_handle_leftover_payload: " "message specified zero SPIs, ignoring")); return -1; } /* verify proper SPI size */ if ((proto == ISAKMP_PROTO_ISAKMP && spisz != ISAKMP_HDR_COOKIES_LEN) || (proto != ISAKMP_PROTO_ISAKMP && spisz != sizeof(u_int32_t))) { log_print("ipsec_handle_leftover_payload: invalid SPI " "size %d for proto %d in DELETE payload", spisz, proto); return -1; } spis = (u_int8_t *) calloc(nspis, spisz); if (!spis) { log_error("ipsec_handle_leftover_payload: malloc " "(%d) failed", nspis * spisz); return -1; } /* extract SPI and get dst address */ memcpy(spis, payload->p + ISAKMP_DELETE_SPI_OFF, nspis * spisz); msg->transport->vtbl->get_dst(msg->transport, &dst); ipsec_delete_spi_list(dst, proto, spis, nspis, "DELETE"); free(spis); payload->flags |= PL_MARK; return 0; case ISAKMP_PAYLOAD_NOTIFY: switch (GET_ISAKMP_NOTIFY_MSG_TYPE(payload->p)) { case IPSEC_NOTIFY_INITIAL_CONTACT: /* * Permit INITIAL-CONTACT if * - this is not an AGGRESSIVE mode exchange * - it is protected by an ISAKMP SA * * XXX Instead of the first condition above, we could * XXX permit this only for phase 2. In the last * XXX packet of main-mode, this payload, while * XXX encrypted, is not part of the hash digest. As * XXX we currently send our own INITIAL-CONTACTs at * XXX this point, this too would need to be changed. */ if (msg->exchange->type == ISAKMP_EXCH_AGGRESSIVE) { log_print("ipsec_handle_leftover_payload: got " "INITIAL-CONTACT in AGGRESSIVE mode"); return -1; } if ((msg->exchange->flags & EXCHANGE_FLAG_ENCRYPT) == 0) { log_print("ipsec_handle_leftover_payload: got " "INITIAL-CONTACT without ISAKMP SA"); return -1; } if ((msg->flags & MSG_AUTHENTICATED) == 0) { log_print("ipsec_handle_leftover_payload: " "got unauthenticated INITIAL-CONTACT"); return -1; } /* * Find out who is sending this and then delete every * SA that is ready. Exchanges will timeout * themselves and then the non-ready SAs will * disappear too. */ msg->transport->vtbl->get_dst(msg->transport, &dst); while ((sa = sa_lookup_by_peer(dst, SA_LEN(dst), 0)) != 0) { /* * Don't delete the current SA -- we received * the notification over it, so it's obviously * still active. We temporarily need to remove * the SA from the list to avoid an endless * loop, but keep a reference so it won't * disappear meanwhile. */ if (sa == msg->isakmp_sa) { sa_reference(sa); sa_remove(sa); reenter = 1; continue; } LOG_DBG((LOG_SA, 30, "ipsec_handle_leftover_payload: " "INITIAL-CONTACT made us delete SA %p", sa)); sa_delete(sa, 0); } if (reenter) { sa_enter(msg->isakmp_sa); sa_release(msg->isakmp_sa); } payload->flags |= PL_MARK; return 0; } } return -1; } /* Return the encryption keylength in octets of the ESP protocol PROTO. */ int ipsec_esp_enckeylength(struct proto *proto) { struct ipsec_proto *iproto = proto->data; /* Compute the keylength to use. */ switch (proto->id) { case IPSEC_ESP_DES: case IPSEC_ESP_DES_IV32: case IPSEC_ESP_DES_IV64: return 8; case IPSEC_ESP_3DES: return 24; case IPSEC_ESP_CAST: if (!iproto->keylen) return 16; return iproto->keylen / 8; case IPSEC_ESP_AES_128_CTR: return 20; case IPSEC_ESP_AES: if (!iproto->keylen) return 16; /* FALLTHROUGH */ default: return iproto->keylen / 8; } } /* Return the authentication keylength in octets of the ESP protocol PROTO. */ int ipsec_esp_authkeylength(struct proto *proto) { struct ipsec_proto *iproto = proto->data; switch (iproto->auth) { case IPSEC_AUTH_HMAC_MD5: return 16; case IPSEC_AUTH_HMAC_SHA: case IPSEC_AUTH_HMAC_RIPEMD: return 20; case IPSEC_AUTH_HMAC_SHA2_256: return 32; case IPSEC_AUTH_HMAC_SHA2_384: return 48; case IPSEC_AUTH_HMAC_SHA2_512: return 64; default: return 0; } } /* Return the authentication keylength in octets of the AH protocol PROTO. */ int ipsec_ah_keylength(struct proto *proto) { switch (proto->id) { case IPSEC_AH_MD5: return 16; case IPSEC_AH_SHA: case IPSEC_AH_RIPEMD: return 20; case IPSEC_AH_SHA2_256: return 32; case IPSEC_AH_SHA2_384: return 48; case IPSEC_AH_SHA2_512: return 64; default: return -1; } } /* Return the total keymaterial length of the protocol PROTO. */ int ipsec_keymat_length(struct proto *proto) { switch (proto->proto) { case IPSEC_PROTO_IPSEC_ESP: return ipsec_esp_enckeylength(proto) + ipsec_esp_authkeylength(proto); case IPSEC_PROTO_IPSEC_AH: return ipsec_ah_keylength(proto); default: return -1; } } /* Helper function for ipsec_get_id(). */ static int ipsec_get_proto_port(char *section, u_int8_t *tproto, u_int16_t *port) { struct protoent *pe = NULL; struct servent *se; char *pstr; pstr = conf_get_str(section, "Protocol"); if (!pstr) { *tproto = 0; return 0; } *tproto = (u_int8_t)atoi(pstr); if (!*tproto) { pe = getprotobyname(pstr); if (pe) *tproto = pe->p_proto; } if (!*tproto) { log_print("ipsec_get_proto_port: protocol \"%s\" unknown", pstr); return -1; } pstr = conf_get_str(section, "Port"); if (!pstr) return 0; *port = (u_int16_t)atoi(pstr); if (!*port) { se = getservbyname(pstr, pe ? pe->p_name : (pstr ? pstr : NULL)); if (se) *port = ntohs(se->s_port); } if (!*port) { log_print("ipsec_get_proto_port: port \"%s\" unknown", pstr); return -1; } return 0; } /* * Out of a named section SECTION in the configuration file find out * the network address and mask as well as the ID type. Put the info * in the areas pointed to by ADDR, MASK, TPROTO, PORT, and ID respectively. * Return 0 on success and -1 on failure. */ int ipsec_get_id(char *section, int *id, struct sockaddr **addr, struct sockaddr **mask, u_int8_t *tproto, u_int16_t *port) { char *type, *address, *netmask; sa_family_t af = 0; type = conf_get_str(section, "ID-type"); if (!type) { log_print("ipsec_get_id: section %s has no \"ID-type\" tag", section); return -1; } *id = constant_value(ipsec_id_cst, type); switch (*id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV4_ADDR_SUBNET: af = AF_INET; break; case IPSEC_ID_IPV6_ADDR: case IPSEC_ID_IPV6_ADDR_SUBNET: af = AF_INET6; break; } switch (*id) { case IPSEC_ID_IPV4_ADDR: case IPSEC_ID_IPV6_ADDR: { int ret; address = conf_get_str(section, "Address"); if (!address) { log_print("ipsec_get_id: section %s has no " "\"Address\" tag", section); return -1; } if (text2sockaddr(address, NULL, addr, af, 0)) { log_print("ipsec_get_id: invalid address %s in " "section %s", address, section); return -1; } ret = ipsec_get_proto_port(section, tproto, port); if (ret < 0) free(*addr); return ret; } #ifdef notyet case IPSEC_ID_FQDN: return -1; case IPSEC_ID_USER_FQDN: return -1; #endif case IPSEC_ID_IPV4_ADDR_SUBNET: case IPSEC_ID_IPV6_ADDR_SUBNET: { int ret; address = conf_get_str(section, "Network"); if (!address) { log_print("ipsec_get_id: section %s has no " "\"Network\" tag", section); return -1; } if (text2sockaddr(address, NULL, addr, af, 0)) { log_print("ipsec_get_id: invalid section %s " "network %s", section, address); return -1; } netmask = conf_get_str(section, "Netmask"); if (!netmask) { log_print("ipsec_get_id: section %s has no " "\"Netmask\" tag", section); free(*addr); return -1; } if (text2sockaddr(netmask, NULL, mask, af, 1)) { log_print("ipsec_get_id: invalid section %s " "network %s", section, netmask); free(*addr); return -1; } ret = ipsec_get_proto_port(section, tproto, port); if (ret < 0) { free(*mask); free(*addr); } return ret; } #ifdef notyet case IPSEC_ID_IPV4_RANGE: return -1; case IPSEC_ID_IPV6_RANGE: return -1; case IPSEC_ID_DER_ASN1_DN: return -1; case IPSEC_ID_DER_ASN1_GN: return -1; case IPSEC_ID_KEY_ID: return -1; #endif default: log_print("ipsec_get_id: unknown ID type \"%s\" in " "section %s", type, section); return -1; } return 0; } /* * XXX I rather want this function to return a status code, and fail if * we cannot fit the information in the supplied buffer. */ static void ipsec_decode_id(char *buf, size_t size, u_int8_t *id, size_t id_len, int isakmpform) { int id_type; char *addr = 0, *mask = 0; u_int32_t *idp; if (id) { if (!isakmpform) { /* * Exchanges and SAs dont carry the IDs in ISAKMP * form. */ id -= ISAKMP_GEN_SZ; id_len += ISAKMP_GEN_SZ; } id_type = GET_ISAKMP_ID_TYPE(id); idp = (u_int32_t *) (id + ISAKMP_ID_DATA_OFF); switch (id_type) { case IPSEC_ID_IPV4_ADDR: util_ntoa(&addr, AF_INET, id + ISAKMP_ID_DATA_OFF); snprintf(buf, size, "%08x: %s", decode_32(id + ISAKMP_ID_DATA_OFF), addr); break; case IPSEC_ID_IPV4_ADDR_SUBNET: util_ntoa(&addr, AF_INET, id + ISAKMP_ID_DATA_OFF); util_ntoa(&mask, AF_INET, id + ISAKMP_ID_DATA_OFF + 4); snprintf(buf, size, "%08x/%08x: %s/%s", decode_32(id + ISAKMP_ID_DATA_OFF), decode_32(id + ISAKMP_ID_DATA_OFF + 4), addr, mask); break; case IPSEC_ID_IPV6_ADDR: util_ntoa(&addr, AF_INET6, id + ISAKMP_ID_DATA_OFF); snprintf(buf, size, "%08x%08x%08x%08x: %s", *idp, *(idp + 1), *(idp + 2), *(idp + 3), addr); break; case IPSEC_ID_IPV6_ADDR_SUBNET: util_ntoa(&addr, AF_INET6, id + ISAKMP_ID_DATA_OFF); util_ntoa(&mask, AF_INET6, id + ISAKMP_ID_DATA_OFF + sizeof(struct in6_addr)); snprintf(buf, size, "%08x%08x%08x%08x/%08x%08x%08x%08x: %s/%s", *idp, *(idp + 1), *(idp + 2), *(idp + 3), *(idp + 4), *(idp + 5), *(idp + 6), *(idp + 7), addr, mask); break; case IPSEC_ID_FQDN: case IPSEC_ID_USER_FQDN: /* String is not NUL terminated, be careful */ id_len -= ISAKMP_ID_DATA_OFF; id_len = MIN(id_len, size - 1); memcpy(buf, id + ISAKMP_ID_DATA_OFF, id_len); buf[id_len] = '\0'; break; case IPSEC_ID_DER_ASN1_DN: addr = x509_DN_string(id + ISAKMP_ID_DATA_OFF, id_len - ISAKMP_ID_DATA_OFF); if (!addr) { snprintf(buf, size, "unparsable ASN1 DN ID"); return; } strlcpy(buf, addr, size); break; default: snprintf(buf, size, "", id_type); break; } } else snprintf(buf, size, ""); free(addr); free(mask); } char * ipsec_decode_ids(char *fmt, u_int8_t *id1, size_t id1_len, u_int8_t *id2, size_t id2_len, int isakmpform) { static char result[1024]; char s_id1[256], s_id2[256]; ipsec_decode_id(s_id1, sizeof s_id1, id1, id1_len, isakmpform); ipsec_decode_id(s_id2, sizeof s_id2, id2, id2_len, isakmpform); snprintf(result, sizeof result, fmt, s_id1, s_id2); return result; } /* * Out of a named section SECTION in the configuration file build an * ISAKMP ID payload. Ths payload size should be stashed in SZ. * The caller is responsible for freeing the payload. */ u_int8_t * ipsec_build_id(char *section, size_t *sz) { struct sockaddr *addr, *mask; u_int8_t *p; int id, subnet = 0; u_int8_t tproto = 0; u_int16_t port = 0; if (ipsec_get_id(section, &id, &addr, &mask, &tproto, &port)) return 0; if (id == IPSEC_ID_IPV4_ADDR_SUBNET || id == IPSEC_ID_IPV6_ADDR_SUBNET) subnet = 1; *sz = ISAKMP_ID_SZ + sockaddr_addrlen(addr); if (subnet) *sz += sockaddr_addrlen(mask); p = malloc(*sz); if (!p) { log_print("ipsec_build_id: malloc(%lu) failed", (unsigned long)*sz); if (subnet) free(mask); free(addr); return 0; } SET_ISAKMP_ID_TYPE(p, id); SET_ISAKMP_ID_DOI_DATA(p, (unsigned char *)"\000\000\000"); memcpy(p + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(addr), sockaddr_addrlen(addr)); if (subnet) memcpy(p + ISAKMP_ID_DATA_OFF + sockaddr_addrlen(addr), sockaddr_addrdata(mask), sockaddr_addrlen(mask)); SET_IPSEC_ID_PROTO(p + ISAKMP_ID_DOI_DATA_OFF, tproto); SET_IPSEC_ID_PORT(p + ISAKMP_ID_DOI_DATA_OFF, port); if (subnet) free(mask); free(addr); return p; } /* * copy an ISAKMPD id */ int ipsec_clone_id(u_int8_t **did, size_t *did_len, u_int8_t *id, size_t id_len) { free(*did); if (!id_len || !id) { *did = 0; *did_len = 0; return 0; } *did = malloc(id_len); if (!*did) { *did_len = 0; log_error("ipsec_clone_id: malloc(%lu) failed", (unsigned long)id_len); return -1; } *did_len = id_len; memcpy(*did, id, id_len); return 0; } /* * IPsec-specific PROTO initializations. SECTION is only set if we are the * initiator thus only usable there. * XXX I want to fix this later. */ void ipsec_proto_init(struct proto *proto, char *section) { struct ipsec_proto *iproto = proto->data; if (proto->sa->phase == 2) iproto->replay_window = section ? conf_get_num(section, "ReplayWindow", DEFAULT_REPLAY_WINDOW) : DEFAULT_REPLAY_WINDOW; } /* * Add a notification payload of type INITIAL CONTACT to MSG if this is * the first contact we have made to our peer. */ int ipsec_initial_contact(struct message *msg) { u_int8_t *buf; if (ipsec_contacted(msg)) return 0; buf = malloc(ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN); if (!buf) { log_error("ike_phase_1_initial_contact: malloc (%d) failed", ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN); return -1; } SET_ISAKMP_NOTIFY_DOI(buf, IPSEC_DOI_IPSEC); SET_ISAKMP_NOTIFY_PROTO(buf, ISAKMP_PROTO_ISAKMP); SET_ISAKMP_NOTIFY_SPI_SZ(buf, ISAKMP_HDR_COOKIES_LEN); SET_ISAKMP_NOTIFY_MSG_TYPE(buf, IPSEC_NOTIFY_INITIAL_CONTACT); memcpy(buf + ISAKMP_NOTIFY_SPI_OFF, msg->isakmp_sa->cookies, ISAKMP_HDR_COOKIES_LEN); if (message_add_payload(msg, ISAKMP_PAYLOAD_NOTIFY, buf, ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN, 1)) { free(buf); return -1; } return ipsec_add_contact(msg); } /* * Compare the two contacts pointed to by A and B. Return negative if * *A < *B, 0 if they are equal, and positive if *A is the largest of them. */ static int addr_cmp(const void *a, const void *b) { const struct contact *x = a, *y = b; int minlen = MIN(x->len, y->len); int rv = memcmp(x->addr, y->addr, minlen); return rv ? rv : (x->len - y->len); } /* * Add the peer that MSG is bound to as an address we don't want to send * INITIAL CONTACT too from now on. Do not call this function with a * specific address duplicate times. We want fast lookup, speed of insertion * is unimportant, if this is to scale. */ static int ipsec_add_contact(struct message *msg) { struct contact *new_contacts; struct sockaddr *dst, *addr; int cnt; if (contact_cnt == contact_limit) { cnt = contact_limit ? 2 * contact_limit : 64; new_contacts = realloc(contacts, cnt * sizeof contacts[0]); if (!new_contacts) { log_error("ipsec_add_contact: " "realloc (%p, %lu) failed", contacts, cnt * (unsigned long) sizeof contacts[0]); return -1; } contact_limit = cnt; contacts = new_contacts; } msg->transport->vtbl->get_dst(msg->transport, &dst); addr = malloc(SA_LEN(dst)); if (!addr) { log_error("ipsec_add_contact: malloc (%lu) failed", (unsigned long)SA_LEN(dst)); return -1; } memcpy(addr, dst, SA_LEN(dst)); contacts[contact_cnt].addr = addr; contacts[contact_cnt++].len = SA_LEN(dst); /* * XXX There are better algorithms for already mostly-sorted data like * this, but only qsort is standard. I will someday do this inline. */ qsort(contacts, contact_cnt, sizeof *contacts, addr_cmp); return 0; } /* Return true if the recipient of MSG has already been contacted. */ static int ipsec_contacted(struct message *msg) { struct contact contact; msg->transport->vtbl->get_dst(msg->transport, &contact.addr); contact.len = SA_LEN(contact.addr); return contacts ? (bsearch(&contact, contacts, contact_cnt, sizeof *contacts, addr_cmp) != 0) : 0; } /* Add a HASH for to MSG. */ u_int8_t * ipsec_add_hash_payload(struct message *msg, size_t hashsize) { u_int8_t *buf; buf = malloc(ISAKMP_HASH_SZ + hashsize); if (!buf) { log_error("ipsec_add_hash_payload: malloc (%lu) failed", ISAKMP_HASH_SZ + (unsigned long) hashsize); return 0; } if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf, ISAKMP_HASH_SZ + hashsize, 1)) { free(buf); return 0; } return buf; } /* Fill in the HASH payload of MSG. */ int ipsec_fill_in_hash(struct message *msg) { struct exchange *exchange = msg->exchange; struct sa *isakmp_sa = msg->isakmp_sa; struct ipsec_sa *isa = isakmp_sa->data; struct hash *hash = hash_get(isa->hash); struct prf *prf; struct payload *payload; u_int8_t *buf; u_int32_t i; char header[80]; /* If no SKEYID_a, we need not do anything. */ if (!isa->skeyid_a) return 0; payload = payload_first(msg, ISAKMP_PAYLOAD_HASH); if (!payload) { log_print("ipsec_fill_in_hash: no HASH payload found"); return -1; } buf = payload->p; /* Allocate the prf and start calculating our HASH(1). */ LOG_DBG_BUF((LOG_MISC, 90, "ipsec_fill_in_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) return -1; prf->Init(prf->prfctx); LOG_DBG_BUF((LOG_MISC, 90, "ipsec_fill_in_hash: message_id", exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN)); prf->Update(prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); /* Loop over all payloads after HASH(1). */ for (i = 2; i < msg->iovlen; i++) { /* XXX Misleading payload type printouts. */ snprintf(header, sizeof header, "ipsec_fill_in_hash: payload %d after HASH(1)", i - 1); LOG_DBG_BUF((LOG_MISC, 90, header, msg->iov[i].iov_base, msg->iov[i].iov_len)); prf->Update(prf->prfctx, msg->iov[i].iov_base, msg->iov[i].iov_len); } prf->Final(buf + ISAKMP_HASH_DATA_OFF, prf->prfctx); prf_free(prf); LOG_DBG_BUF((LOG_MISC, 80, "ipsec_fill_in_hash: HASH(1)", buf + ISAKMP_HASH_DATA_OFF, hash->hashsize)); return 0; } /* Add a HASH payload to MSG, if we have an ISAKMP SA we're protected by. */ static int ipsec_informational_pre_hook(struct message *msg) { struct sa *isakmp_sa = msg->isakmp_sa; struct ipsec_sa *isa; struct hash *hash; if (!isakmp_sa) return 0; isa = isakmp_sa->data; hash = hash_get(isa->hash); return ipsec_add_hash_payload(msg, hash->hashsize) == 0; } /* * Fill in the HASH payload in MSG, if we have an ISAKMP SA we're protected by. */ static int ipsec_informational_post_hook(struct message *msg) { if (!msg->isakmp_sa) return 0; return ipsec_fill_in_hash(msg); } ssize_t ipsec_id_size(char *section, u_int8_t *id) { char *type, *data; type = conf_get_str(section, "ID-type"); if (!type) { log_print("ipsec_id_size: section %s has no \"ID-type\" tag", section); return -1; } *id = constant_value(ipsec_id_cst, type); switch (*id) { case IPSEC_ID_IPV4_ADDR: return sizeof(struct in_addr); case IPSEC_ID_IPV4_ADDR_SUBNET: return 2 * sizeof(struct in_addr); case IPSEC_ID_IPV6_ADDR: return sizeof(struct in6_addr); case IPSEC_ID_IPV6_ADDR_SUBNET: return 2 * sizeof(struct in6_addr); case IPSEC_ID_FQDN: case IPSEC_ID_USER_FQDN: case IPSEC_ID_KEY_ID: case IPSEC_ID_DER_ASN1_DN: case IPSEC_ID_DER_ASN1_GN: data = conf_get_str(section, "Name"); if (!data) { log_print("ipsec_id_size: " "section %s has no \"Name\" tag", section); return -1; } return strlen(data); } log_print("ipsec_id_size: unrecognized/unsupported ID-type %d (%s)", *id, type); return -1; } /* * Generate a string version of the ID. */ char * ipsec_id_string(u_int8_t *id, size_t id_len) { char *buf = 0; char *addrstr = 0; size_t len, size; /* * XXX Real ugly way of making the offsets correct. Be aware that id * now will point before the actual buffer and cannot be dereferenced * without an offset larger than or equal to ISAKM_GEN_SZ. */ id -= ISAKMP_GEN_SZ; /* This is the actual length of the ID data field. */ id_len += ISAKMP_GEN_SZ - ISAKMP_ID_DATA_OFF; /* * Conservative allocation. * XXX I think the ASN1 DN case can be thought through to give a better * estimate. */ size = MAX(sizeof "ipv6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", sizeof "asn1_dn/" + id_len - ISAKMP_ID_DATA_OFF); buf = malloc(size); if (!buf) /* XXX Log? */ goto fail; switch (GET_ISAKMP_ID_TYPE(id)) { case IPSEC_ID_IPV4_ADDR: if (id_len < sizeof(struct in_addr)) goto fail; util_ntoa(&addrstr, AF_INET, id + ISAKMP_ID_DATA_OFF); if (!addrstr) goto fail; snprintf(buf, size, "ipv4/%s", addrstr); break; case IPSEC_ID_IPV6_ADDR: if (id_len < sizeof(struct in6_addr)) goto fail; util_ntoa(&addrstr, AF_INET6, id + ISAKMP_ID_DATA_OFF); if (!addrstr) goto fail; snprintf(buf, size, "ipv6/%s", addrstr); break; case IPSEC_ID_FQDN: case IPSEC_ID_USER_FQDN: strlcpy(buf, GET_ISAKMP_ID_TYPE(id) == IPSEC_ID_FQDN ? "fqdn/" : "ufqdn/", size); len = strlen(buf); memcpy(buf + len, id + ISAKMP_ID_DATA_OFF, id_len); *(buf + len + id_len) = '\0'; break; case IPSEC_ID_DER_ASN1_DN: strlcpy(buf, "asn1_dn/", size); len = strlen(buf); addrstr = x509_DN_string(id + ISAKMP_ID_DATA_OFF, id_len - ISAKMP_ID_DATA_OFF); if (!addrstr) goto fail; if (size < len + strlen(addrstr) + 1) goto fail; strlcpy(buf + len, addrstr, size - len); break; default: /* Unknown type. */ LOG_DBG((LOG_MISC, 10, "ipsec_id_string: unknown identity type %d\n", GET_ISAKMP_ID_TYPE(id))); goto fail; } free(addrstr); return buf; fail: free(buf); free(addrstr); return 0; }