/* $openbsd: isakmp_cfg.c,v 1.13 2002/06/08 17:35:06 ho Exp $ */ /* * Copyright (c) 2001 Niklas Hallqvist. All rights reserved. * Copyright (c) 2002 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Ericsson Radio Systems. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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 Gatespace * (http://www.gatespace.com/). */ #include #include #include #include #include #include #include "sysdep.h" #include "attribute.h" #include "conf.h" #include "exchange.h" #include "hash.h" #include "ipsec.h" #include "isakmp_fld.h" #include "isakmp_num.h" #include "log.h" #include "message.h" #include "prf.h" #include "sa.h" #include "transport.h" #include "util.h" /* * Validation script used to test messages for correct content of * payloads depending on the exchange type. */ int16_t script_transaction[] = { ISAKMP_PAYLOAD_ATTRIBUTE, /* Initiator -> responder. */ EXCHANGE_SCRIPT_SWITCH, ISAKMP_PAYLOAD_ATTRIBUTE, /* Responder -> initiator. */ EXCHANGE_SCRIPT_END }; static int cfg_decode_attribute (u_int16_t, u_int8_t *, u_int16_t, void *); static int cfg_encode_attributes (struct isakmp_cfg_attr_head *, u_int32_t, u_int32_t, char *, u_int8_t **, u_int16_t *); static int cfg_initiator_send_ATTR (struct message *); static int cfg_initiator_recv_ATTR (struct message *); static int cfg_responder_recv_ATTR (struct message *); static int cfg_responder_send_ATTR (struct message *); u_int8_t *cfg_add_hash (struct message *); int cfg_finalize_hash (struct message *,u_int8_t *, u_int8_t *, u_int16_t); int cfg_verify_hash (struct message *msg); /* Server: SET/ACK Client; REQ/REPLY */ int (*isakmp_cfg_initiator[]) (struct message *) = { cfg_initiator_send_ATTR, cfg_initiator_recv_ATTR }; /* Server: REQ/REPLY Client: SET/ACK */ int (*isakmp_cfg_responder[]) (struct message *) = { cfg_responder_recv_ATTR, cfg_responder_send_ATTR }; /* * When we are "the server", this starts SET/ACK mode * When we are "the client", this starts REQ/REPLY mode */ static int cfg_initiator_send_ATTR (struct message *msg) { struct sa *isakmp_sa = msg->isakmp_sa; struct ipsec_exch *ie = msg->exchange->data; u_int8_t *hashp = 0, *attrp, *attr; size_t attrlen, off; char *id_string, *cfg_mode, *field; struct sockaddr *sa; #define CFG_ATTR_BIT_MAX ISAKMP_CFG_ATTR_FUTURE_MIN /* XXX */ bitstr_t bit_decl (attrbits, CFG_ATTR_BIT_MAX); u_int16_t bit, length; u_int32_t life; if (msg->exchange->phase == 2) { hashp = cfg_add_hash (msg); if (!hashp) return -1; } /* We inititated of this exchange, check isakmp_sa for other side. */ 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 (!id_string) { log_print ("cfg_initiator_send_ATTR: cannot parse ID"); goto fail; } /* Check for attribute list to send to the other side */ attrlen = 0; bit_nclear (attrbits, 0, CFG_ATTR_BIT_MAX - 1); cfg_mode = conf_get_str (id_string, "Mode"); if (!cfg_mode || strcmp (cfg_mode, "SET") == 0) { /* SET/ACK mode */ ie->cfg_type = ISAKMP_CFG_SET; LOG_DBG ((LOG_NEGOTIATION, 10, "cfg_initiator_send_ATTR: SET/ACK mode")); #define ATTRFIND(STR,ATTR4,LEN4,ATTR6,LEN6) do \ { \ if ((sa = conf_get_address (id_string, STR)) != NULL) \ switch (sa->sa_family) \ { \ case AF_INET: \ bit_set (attrbits, ATTR4); \ attrlen += ISAKMP_ATTR_SZ + LEN4; \ break; \ case AF_INET6: \ bit_set (attrbits, ATTR6); \ attrlen += ISAKMP_ATTR_SZ + LEN6; \ break; \ default: \ } \ free (sa); \ } while (0) /* XXX We don't simultaneously support IPv4 and IPv6 addresses. */ ATTRFIND ("Address", ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS, 4, ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS, 16); ATTRFIND ("Netmask", ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK, 4, ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK, 16); ATTRFIND ("Nameserver", ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS, 4, ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS, 16); ATTRFIND ("WINS-server", ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS, 4, ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS, 16); ATTRFIND ("DHCP-server", ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP, 4, ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP, 16); #ifdef notyet ATTRFIND ("Network", ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET, 8, ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET, 17); #endif #undef ATTRFIND if (conf_get_str (id_string, "Lifetime")) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY); attrlen += ISAKMP_ATTR_SZ + 4; } } else { struct conf_list *alist; struct conf_list_node *anode; ie->cfg_type = ISAKMP_CFG_REQUEST; LOG_DBG ((LOG_NEGOTIATION, 10, "cfg_initiator_send_ATTR: REQ/REPLY mode")); alist = conf_get_list (id_string, "Attributes"); if (alist) { for (anode = TAILQ_FIRST (&alist->fields); anode; anode = TAILQ_NEXT (anode, link)) { if (strcasecmp (anode->field, "Address") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS); bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS); attrlen += ISAKMP_ATTR_SZ * 2; } else if (strcasecmp (anode->field, "Netmask") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK); bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK); attrlen += ISAKMP_ATTR_SZ * 2; } else if (strcasecmp (anode->field, "Nameserver") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS); bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS); attrlen += ISAKMP_ATTR_SZ * 2; } else if (strcasecmp (anode->field, "WINS-server") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS); bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS); attrlen += ISAKMP_ATTR_SZ * 2; } else if (strcasecmp (anode->field, "DHCP-server") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP); bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP); attrlen += ISAKMP_ATTR_SZ * 2; } else if (strcasecmp (anode->field, "Lifetime") == 0) { bit_set (attrbits, ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY); attrlen += ISAKMP_ATTR_SZ; } else { log_print ("cfg_initiator_send_ATTR: unknown attribute " "%.20s in section [%s]", anode->field, id_string); } } conf_free_list (alist); } } if (attrlen == 0) { /* No data found. */ log_print ("cfg_initiator_send_ATTR: no IKECFG attributes " "found for [%s]", id_string); /* * We can continue, but this indicates a configuration error that * the user probably will want to correct. */ free (id_string); return 0; } attrlen += ISAKMP_ATTRIBUTE_SZ; attrp = calloc (1, attrlen); if (!attrp) { log_error ("cfg_initiator_send_ATTR: calloc (1, %lu) failed", (unsigned long)attrlen); goto fail; } if (message_add_payload (msg, ISAKMP_PAYLOAD_ATTRIBUTE, attrp, attrlen, 1)) { free (attrp); goto fail; } SET_ISAKMP_ATTRIBUTE_TYPE (attrp, ie->cfg_type); getrandom ((u_int8_t *)&ie->cfg_id, sizeof ie->cfg_id); SET_ISAKMP_ATTRIBUTE_ID (attrp, ie->cfg_id); off = ISAKMP_ATTRIBUTE_SZ; /* * Use the bitstring built previously to collect the right * parameters for attrp. */ for (bit = 0; bit < CFG_ATTR_BIT_MAX; bit++) if (bit_test (attrbits, bit)) { attr = attrp + off; SET_ISAKMP_ATTR_TYPE (attr, bit); if (ie->cfg_type == ISAKMP_CFG_REQUEST) { off += ISAKMP_ATTR_SZ; continue; } /* All the other are similar, this is the odd one. */ if (bit == ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY) { life = conf_get_num (id_string, "Lifetime", 1200); SET_ISAKMP_ATTR_LENGTH_VALUE (attr, 4); encode_32 (attr + ISAKMP_ATTR_VALUE_OFF, life); off += ISAKMP_ATTR_SZ + 4; continue; } switch (bit) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: length = 4; break; case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: length = 16; break; default: length = 0; /* Silence gcc. */ } switch (bit) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: field = "Address"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: field = "Netmask"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: field = "Nameserver"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: field = "DHCP-server"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: field = "WINS-server"; break; default: field = 0; /* Silence gcc. */ } sa = conf_get_address (id_string, field); SET_ISAKMP_ATTR_LENGTH_VALUE (attr, length); memcpy (attr + ISAKMP_ATTR_VALUE_OFF, sockaddr_addrdata (sa), length); off += ISAKMP_ATTR_SZ + length; } if (msg->exchange->phase == 2) if (cfg_finalize_hash (msg, hashp, attrp, attrlen)) goto fail; return 0; fail: if (id_string) free (id_string); return -1; } /* * As "the server", this ends SET/ACK. * As "the client", this ends REQ/REPLY. */ static int cfg_initiator_recv_ATTR (struct message *msg) { struct payload *attrp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_ATTRIBUTE]); struct ipsec_exch *ie = msg->exchange->data; struct sa *isakmp_sa = msg->isakmp_sa; struct isakmp_cfg_attr *attr; struct sockaddr *sa; const char *uk_addr = ""; char *addr; if (msg->exchange->phase == 2) if (cfg_verify_hash (msg)) return -1; /* Sanity. */ if (ie->cfg_id != GET_ISAKMP_ATTRIBUTE_ID (attrp->p)) { log_print ("cfg_initiator_recv_ATTR: cfg packet ID does not match!"); message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); return -1; } switch (attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]) { case ISAKMP_CFG_ACK: if (ie->cfg_type != ISAKMP_CFG_SET) { log_print ("cfg_initiator_recv_ATTR: bad packet type ACK"); message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); return -1; } break; case ISAKMP_CFG_REPLY: if (ie->cfg_type != ISAKMP_CFG_REQUEST) { log_print ("cfg_initiator_recv_ATTR: bad packet type REPLY"); message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); return -1; } break; default: log_print ("cfg_initiator_recv_ATTR: " "unexpected configuration message type %d", attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]); message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); return -1; } attribute_map (attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF, GET_ISAKMP_GEN_LENGTH (attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, cfg_decode_attribute, ie); switch (ie->cfg_type) { case ISAKMP_CFG_ACK: { /* SET/ACK -- Server side (ACK from client) */ msg->transport->vtbl->get_src (isakmp_sa->transport, &sa); if (sockaddr2text (sa, &addr, 0) < 0) addr = (char *)uk_addr; for (attr = LIST_FIRST (&ie->attrs); attr; attr = LIST_NEXT (attr, link)) LOG_DBG ((LOG_NEGOTIATION, 50, "cfg_initiator_recv_ATTR: " "client %s ACKs attribute %s", addr, constant_name (isakmp_cfg_attr_cst, attr->type))); if (addr != uk_addr) free (addr); } break; case ISAKMP_CFG_REPLY: { /* REQ/REPLY: effect attributes we've gotten responses on. */ msg->transport->vtbl->get_src (isakmp_sa->transport, &sa); if (sockaddr2text (sa, &addr, 0) < 0) addr = (char *)uk_addr; for (attr = LIST_FIRST (&ie->attrs); attr; attr = LIST_NEXT (attr, link)) LOG_DBG ((LOG_NEGOTIATION, 50, "cfg_initiator_recv_ATTR: " "server %s replied with attribute %s", addr, constant_name (isakmp_cfg_attr_cst, attr->type))); if (addr != uk_addr) free (addr); } break; default: break; } attrp->flags |= PL_MARK; return 0; } /* * As "the server", this starts REQ/REPLY (initiated by the client). * As "the client", this starts SET/ACK (initiated by the server). */ static int cfg_responder_recv_ATTR (struct message *msg) { struct payload *attrp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_ATTRIBUTE]); struct ipsec_exch *ie = msg->exchange->data; struct sa *isakmp_sa = msg->isakmp_sa; struct isakmp_cfg_attr *attr; struct sockaddr *sa; char *addr; if (msg->exchange->phase == 2) if (cfg_verify_hash (msg)) return -1; ie->cfg_id = GET_ISAKMP_ATTRIBUTE_ID (attrp->p); ie->cfg_type = attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]; switch (ie->cfg_type) { case ISAKMP_CFG_REQUEST: case ISAKMP_CFG_SET: break; default: message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0); log_print ("cfg_responder_recv_ATTR: " "unexpected configuration message type %d", ie->cfg_type); return -1; } attribute_map (attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF, GET_ISAKMP_GEN_LENGTH (attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, cfg_decode_attribute, ie); switch (ie->cfg_type) { case ISAKMP_CFG_REQUEST: /* We're done. */ break; case ISAKMP_CFG_SET: { /* SET/ACK -- Client side (SET from server) */ const char *uk_addr = ""; msg->transport->vtbl->get_dst (isakmp_sa->transport, &sa); if (sockaddr2text (sa, &addr, 0) < 0) addr = (char *)uk_addr; for (attr = LIST_FIRST (&ie->attrs); attr; attr = LIST_NEXT (attr, link)) LOG_DBG ((LOG_NEGOTIATION, 50, "cfg_responder_recv_ATTR: " "server %s asks us to SET attribute %s", addr, constant_name (isakmp_cfg_attr_cst, attr->type))); /* * XXX Here's the place to add code to walk through each attribute * XXX and send them along to dhclient or whatever. Each attribute * XXX that we act upon (such as setting a netmask), should be * XXX marked like this for us to send the proper ACK response: * XXX attr->attr_used++; */ if (addr != uk_addr) free (addr); } break; default: break; } attrp->flags |= PL_MARK; return 0; } /* * As "the server", this ends REQ/REPLY mode. * As "the client", this ends SET/ACK mode. */ static int cfg_responder_send_ATTR (struct message *msg) { struct ipsec_exch *ie = msg->exchange->data; struct sa *isakmp_sa = msg->isakmp_sa; u_int8_t *hashp = 0, *attrp; u_int16_t attrlen; char *id_string; if (msg->exchange->phase == 2) { hashp = cfg_add_hash (msg); if (!hashp) return -1; } /* We are responder, check isakmp_sa for other side. */ if (isakmp_sa->initiator) id_string = ipsec_id_string (isakmp_sa->id_i, isakmp_sa->id_i_len); else id_string = ipsec_id_string (isakmp_sa->id_r, isakmp_sa->id_r_len); if (!id_string) { log_print ("cfg_responder_send_ATTR: cannot parse client's ID"); return -1; } if (cfg_encode_attributes (&ie->attrs, (ie->cfg_type == ISAKMP_CFG_SET ? ISAKMP_CFG_ACK : ISAKMP_CFG_REPLY), ie->cfg_id, id_string, &attrp, &attrlen)) { free (id_string); return -1; } free (id_string); if (message_add_payload (msg, ISAKMP_PAYLOAD_ATTRIBUTE, attrp, attrlen, 1)) { free (attrp); return -1; } if (msg->exchange->phase == 2) if (cfg_finalize_hash (msg, hashp, attrp, attrlen)) return -1; return 0; } u_int8_t * cfg_add_hash (struct message *msg) { struct ipsec_sa *isa = msg->isakmp_sa->data; struct hash *hash = hash_get (isa->hash); u_int8_t *hashp; hashp = malloc (ISAKMP_HASH_SZ + hash->hashsize); if (!hashp) { log_error ("cfg_add_hash: malloc (%lu) failed", ISAKMP_HASH_SZ + (unsigned long)hash->hashsize); return 0; } if (message_add_payload (msg, ISAKMP_PAYLOAD_HASH, hashp, ISAKMP_HASH_SZ + hash->hashsize, 1)) { free (hashp); return 0; } return hashp; } int cfg_finalize_hash (struct message *msg, u_int8_t *hashp, u_int8_t *data, u_int16_t length) { struct ipsec_sa *isa = msg->isakmp_sa->data; struct prf *prf; prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len); if (!prf) return -1; prf->Init (prf->prfctx); prf->Update (prf->prfctx, msg->exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); prf->Update (prf->prfctx, data, length); prf->Final (hashp + ISAKMP_GEN_SZ, prf->prfctx); prf_free (prf); return 0; } int cfg_verify_hash (struct message *msg) { struct payload *hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]); struct ipsec_sa *isa = msg->isakmp_sa->data; struct prf *prf; u_int8_t *hash, *comp_hash; size_t hash_len; if (!hashp) { log_print ("cfg_verify_hash: phase 2 message missing HASH"); message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 0); return -1; } hash = hashp->p; hash_len = GET_ISAKMP_GEN_LENGTH (hash); comp_hash = malloc (hash_len - ISAKMP_GEN_SZ); if (!comp_hash) { log_error ("cfg_verify_hash: malloc (%lu) failed", (unsigned long)hash_len - ISAKMP_GEN_SZ); return -1; } /* Verify hash. */ prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len); if (!prf) { free (comp_hash); return -1; } prf->Init (prf->prfctx); prf->Update (prf->prfctx, msg->exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); prf->Update (prf->prfctx, hash + hash_len, msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len); prf->Final (comp_hash, prf->prfctx); prf_free (prf); if (memcmp (hash + ISAKMP_GEN_SZ, comp_hash, hash_len - ISAKMP_GEN_SZ) != 0) { message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 0); free (comp_hash); return -1; } free (comp_hash); /* Mark the HASH as handled. */ hashp->flags |= PL_MARK; return 0; } /* * Decode the attribute of type TYPE with a LEN length value pointed to by * VALUE. VIE is a pointer to the IPsec exchange context holding the * attributes indexed by type for easy retrieval. */ static int cfg_decode_attribute (u_int16_t type, u_int8_t *value, u_int16_t len, void *vie) { struct ipsec_exch *ie = vie; struct isakmp_cfg_attr *attr; if (type >= ISAKMP_CFG_ATTR_PRIVATE_MIN && type <= ISAKMP_CFG_ATTR_PRIVATE_MAX) return 0; if (type == 0 || type >= ISAKMP_CFG_ATTR_FUTURE_MIN) { LOG_DBG ((LOG_NEGOTIATION, 30, "cfg_decode_attribute: invalid attr type %u", type)); return -1; } attr = calloc (1, sizeof *attr); if (!attr) { log_error ("cfg_decode_attribute: calloc (1, %lu) failed", (unsigned long)sizeof *attr); return -1; } attr->type = type; attr->length = len; if (len) { attr->value = malloc (len); if (!attr->value) { log_error ("cfg_decode_attribute: malloc (%d) failed", len); free (attr); /* Should we also deallocate all other values? */ return -1; } memcpy (attr->value, value, len); } LIST_INSERT_HEAD (&ie->attrs, attr, link); return 0; } /* * Encode list of attributes from ie->attrs into a attribute payload. */ static int cfg_encode_attributes (struct isakmp_cfg_attr_head *attrs, u_int32_t type, u_int32_t cfg_id, char *id_string, u_int8_t **attrp, u_int16_t *len) { struct isakmp_cfg_attr *attr; struct sockaddr *sa; sa_family_t family; u_int32_t value; u_int16_t off; char *field; /* Compute length */ *len = ISAKMP_ATTRIBUTE_SZ; for (attr = LIST_FIRST (attrs); attr; attr = LIST_NEXT (attr, link)) { /* With ACK we only include the attrs we've actually used. */ if (type == ISAKMP_CFG_ACK && attr->attr_used == 0) continue; switch (attr->type) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: case ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY: attr->length = 4; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET: attr->length = 8; break; case ISAKMP_CFG_ATTR_APPLICATION_VERSION: /* XXX So far no version identifier of isakmpd here. */ attr->length = 0; break; case ISAKMP_CFG_ATTR_SUPPORTED_ATTRIBUTES: attr->length = 2 * 15; break; case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: attr->length = 16; break; case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET: attr->length = 17; break; default: attr->ignore++; /* XXX Log! */ } *len += ISAKMP_ATTR_SZ + attr->length; } /* Allocate enough space for the payload */ *attrp = calloc (1, *len); if (!*attrp) { log_error ("cfg_encode_attributes: calloc (1, %lu) failed", (unsigned long)*len); return -1; } SET_ISAKMP_ATTRIBUTE_TYPE (*attrp, type); SET_ISAKMP_ATTRIBUTE_ID (*attrp, cfg_id); off = ISAKMP_ATTRIBUTE_SZ; for (attr = LIST_FIRST (attrs); attr; attr = LIST_NEXT (attr, link)) { /* With ACK we only include the attrs we've actually used. */ if (type == ISAKMP_CFG_ACK && attr->attr_used == 0) continue; switch (attr->type) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: family = AF_INET; break; case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: family = AF_INET6; break; default: family = 0; break; } switch (attr->type) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: field = "Address"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET: case ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET: field = "Network"; /* XXX or just "Address" */ break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: field = "Netmask"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: field = "DHCP-server"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: field = "Nameserver"; break; case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: field = "WINS-server"; break; default: field = 0; } switch (attr->type) { case ISAKMP_CFG_ATTR_INTERNAL_IP4_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_ADDRESS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NETMASK: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DHCP: case ISAKMP_CFG_ATTR_INTERNAL_IP4_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_DNS: case ISAKMP_CFG_ATTR_INTERNAL_IP4_NBNS: case ISAKMP_CFG_ATTR_INTERNAL_IP6_NBNS: sa = conf_get_address (id_string, field); if (!sa) { LOG_DBG ((LOG_NEGOTIATION, 10, "cfg_responder_send_ATTR: " "attribute not found: %s", field)); attr->length = 0; break; } if (sa->sa_family != family) { log_print ("cfg_responder_send_ATTR: attribute %s - expected %s " "got %s data", field, (family == AF_INET ? "IPv4" : "IPv6"), (sa->sa_family == AF_INET ? "IPv4" : "IPv6")); free (sa); attr->length = 0; break; } /* Temporary limit length for the _SUBNET types. */ if (attr->type == ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET) attr->length = 4; else if (attr->type == ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET) attr->length = 16; memcpy (*attrp + off + ISAKMP_ATTR_VALUE_OFF, sockaddr_addrdata (sa), attr->length); free (sa); /* _SUBNET types need some extra work. */ if (attr->type == ISAKMP_CFG_ATTR_INTERNAL_IP4_SUBNET) { sa = conf_get_address (id_string, "Netmask"); if (!sa) { LOG_DBG ((LOG_NEGOTIATION, 10, "cfg_responder_send_ATTR: " "attribute not found: Netmask")); attr->length = 0; break; } if (sa->sa_family != AF_INET) { log_print ("cfg_responder_send_ATTR: attribute Netmask - " "expected IPv4 got IPv6 data"); free (sa); attr->length = 0; break; } memcpy (*attrp + off + ISAKMP_ATTR_VALUE_OFF + attr->length, sockaddr_addrdata (sa), attr->length); attr->length = 8; free (sa); } else if (attr->type == ISAKMP_CFG_ATTR_INTERNAL_IP6_SUBNET) { int prefix = conf_get_num (id_string, "Prefix", -1); if (prefix == -1) { log_print ("cfg_responder_send_ATTR: " "attribute not found: Prefix"); attr->length = 0; break; } else if (prefix < -1 || prefix > 128) { log_print ("cfg_responder_send_ATTR: attribute Prefix - " "invalid value %d", prefix); attr->length = 0; break; } *(*attrp + off + ISAKMP_ATTR_VALUE_OFF + 16) = (u_int8_t)prefix; attr->length = 17; } break; case ISAKMP_CFG_ATTR_INTERNAL_ADDRESS_EXPIRY: value = conf_get_num (id_string, "Lifetime", 1200); encode_32 (*attrp + off + ISAKMP_ATTR_VALUE_OFF, value); break; case ISAKMP_CFG_ATTR_APPLICATION_VERSION: /* XXX So far no version identifier of isakmpd here. */ break; case ISAKMP_CFG_ATTR_SUPPORTED_ATTRIBUTES: break; default: break; } SET_ISAKMP_ATTR_LENGTH_VALUE (*attrp + off, attr->length); } return 0; }