/* * Copyright (C) 2012 Martin Willi * Copyright (C) 2012 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "eap_radius_forward.h" #include #include #include #include typedef struct private_eap_radius_forward_t private_eap_radius_forward_t; /** * Private data of an eap_radius_forward_t object. */ struct private_eap_radius_forward_t { /** * Public eap_radius_forward_t interface. */ eap_radius_forward_t public; /** * List of attribute types to copy from IKE, as attr_t */ linked_list_t *from_attr; /** * List of attribute types to copy to IKE, as attr_t */ linked_list_t *to_attr; /** * Queued to forward from IKE, unique_id => linked_list_t of chunk_t */ hashtable_t *from; /** * Queued to forward to IKE, unique_id => linked_list_t of chunk_t */ hashtable_t *to; /** * Mutex to lock concurrent access to hashtables */ mutex_t *mutex; }; /** * RADIUS attribute selector */ typedef struct { /** vendor ID, 0 for standard attributes */ u_int32_t vendor; /** attribute type */ u_int8_t type; } attr_t; /** * Single instance of this */ static private_eap_radius_forward_t *singleton = NULL; /** * Hashtable hash function */ static u_int hash(uintptr_t key) { return key; } /** * Hashtable equals function */ static bool equals(uintptr_t a, uintptr_t b) { return a == b; } /** * Free a queue entry */ static void free_attribute(chunk_t *chunk) { free(chunk->ptr); free(chunk); } /** * Lookup/create an attribute queue from a table */ static linked_list_t *lookup_queue(private_eap_radius_forward_t *this, hashtable_t *table) { linked_list_t *queue = NULL; ike_sa_t *ike_sa; uintptr_t id; ike_sa = charon->bus->get_sa(charon->bus); if (ike_sa && ike_sa->supports_extension(ike_sa, EXT_STRONGSWAN)) { id = ike_sa->get_unique_id(ike_sa); this->mutex->lock(this->mutex); queue = table->get(table, (void*)id); if (!queue) { queue = linked_list_create(); table->put(table, (void*)id, queue); } this->mutex->unlock(this->mutex); } return queue; } /** * Remove attribute queue from table */ static void remove_queue(private_eap_radius_forward_t *this, hashtable_t *table, ike_sa_t *ike_sa) { linked_list_t *queue; this->mutex->lock(this->mutex); queue = table->remove(table, (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa)); this->mutex->unlock(this->mutex); if (queue) { queue->destroy_function(queue, (void*)free_attribute); } } /** * Check if RADIUS attribute is contained in selector */ static bool is_attribute_selected(linked_list_t *selector, radius_attribute_type_t type, chunk_t data) { enumerator_t *enumerator; u_int32_t vendor = 0; attr_t *sel; bool found = FALSE; if (type == RAT_VENDOR_SPECIFIC) { if (data.len < 4) { return FALSE; } vendor = untoh32(data.ptr); } enumerator = selector->create_enumerator(selector); while (!found && enumerator->enumerate(enumerator, &sel)) { if (sel->vendor == vendor) { if (vendor) { if (sel->type == 0) { /* any of that vendor is fine */ found = TRUE; } else if (data.len > 4 && data.ptr[4] == sel->type) { /* vendor specific type field, as defined in RFC 2865 */ found = TRUE; } } else { if (sel->type == type) { found = TRUE; } } } } enumerator->destroy(enumerator); return found; } /** * Copy RADIUS attributes from queue to a RADIUS message */ static void queue2radius(linked_list_t *queue, radius_message_t *message) { chunk_t *data; while (queue->remove_last(queue, (void**)&data) == SUCCESS) { if (data->len >= 2) { message->add(message, data->ptr[0], chunk_skip(*data, 2)); } free_attribute(data); } } /** * Copy RADIUS attributes from a RADIUS message to the queue */ static void radius2queue(radius_message_t *message, linked_list_t *queue, linked_list_t *selector) { enumerator_t *enumerator; int type; chunk_t data, hdr, *ptr; enumerator = message->create_enumerator(message); while (enumerator->enumerate(enumerator, &type, &data)) { if (is_attribute_selected(selector, type, data)) { hdr = chunk_alloc(2); hdr.ptr[0] = type; hdr.ptr[1] = data.len + 2; INIT(ptr); *ptr = chunk_cat("mc", hdr, data); queue->insert_last(queue, ptr); } } enumerator->destroy(enumerator); } /** * Copy RADIUS attribute nofifies from IKE message to queue */ static void ike2queue(message_t *message, linked_list_t *queue, linked_list_t *selector) { enumerator_t *enumerator; payload_t *payload; notify_payload_t *notify; chunk_t data, *ptr; enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) { if (payload->get_type(payload) == NOTIFY || payload->get_type(payload) == NOTIFY_V1) { notify = (notify_payload_t*)payload; if (notify->get_notify_type(notify) == RADIUS_ATTRIBUTE) { data = notify->get_notification_data(notify); if (data.len >= 2 && is_attribute_selected(selector, data.ptr[0], chunk_skip(data, 2))) { INIT(ptr); *ptr = chunk_clone(data); queue->insert_last(queue, ptr); } } } } enumerator->destroy(enumerator); } /** * Copy RADUIS attributes from queue to IKE message notifies */ static void queue2ike(linked_list_t *queue, message_t *message) { chunk_t *data; while (queue->remove_last(queue, (void**)&data) == SUCCESS) { message->add_notify(message, FALSE, RADIUS_ATTRIBUTE, *data); free_attribute(data); } } /** * See header. */ void eap_radius_forward_from_ike(radius_message_t *request) { private_eap_radius_forward_t *this = singleton; linked_list_t *queue; if (this) { queue = lookup_queue(this, this->from); if (queue) { queue2radius(queue, request); } } } /** * See header. */ void eap_radius_forward_to_ike(radius_message_t *response) { private_eap_radius_forward_t *this = singleton; linked_list_t *queue; if (this) { queue = lookup_queue(this, this->to); if (queue) { radius2queue(response, queue, this->to_attr); } } } METHOD(listener_t, message, bool, private_eap_radius_forward_t *this, ike_sa_t *ike_sa, message_t *message, bool incoming, bool plain) { linked_list_t *queue; if (plain && message->get_exchange_type(message) == IKE_AUTH) { if (incoming) { queue = lookup_queue(this, this->from); if (queue) { ike2queue(message, queue, this->from_attr); } } else { queue = lookup_queue(this, this->to); if (queue) { queue2ike(queue, message); } } } return TRUE; } METHOD(listener_t, ike_updown, bool, private_eap_radius_forward_t *this, ike_sa_t *ike_sa, bool up) { /* up or down, we don't need the state anymore */ remove_queue(this, this->from, ike_sa); remove_queue(this, this->to, ike_sa); return TRUE; } /** * Parse a selector string to a list of attr_t selectors */ static linked_list_t* parse_selector(char *selector) { enumerator_t *enumerator; linked_list_t *list; char *token, *pos; list = linked_list_create(); enumerator = enumerator_create_token(selector, ",", " "); while (enumerator->enumerate(enumerator, &token)) { int type, vendor = 0; attr_t *attr; pos = strchr(token, ':'); if (pos) { *(pos++) = 0; vendor = atoi(token); token = pos; } type = enum_from_name(radius_attribute_type_names, token); if (type == -1) { type = atoi(token); } if (vendor == 0 && type == 0) { DBG1(DBG_CFG, "ignoring unknown RADIUS attribute type '%s'", token); } else { INIT(attr, .type = type, .vendor = vendor, ); list->insert_last(list, attr); if (!vendor) { DBG1(DBG_IKE, "forwarding RADIUS attribute %N", radius_attribute_type_names, type); } else { DBG1(DBG_IKE, "forwarding RADIUS VSA %d-%d", vendor, type); } } } enumerator->destroy(enumerator); return list; } METHOD(eap_radius_forward_t, destroy, void, private_eap_radius_forward_t *this) { this->from_attr->destroy_function(this->from_attr, free); this->to_attr->destroy_function(this->to_attr, free); this->from->destroy(this->from); this->to->destroy(this->to); this->mutex->destroy(this->mutex); free(this); singleton = NULL; } /** * See header */ eap_radius_forward_t *eap_radius_forward_create() { private_eap_radius_forward_t *this; INIT(this, .public = { .listener = { .message = _message, .ike_updown = _ike_updown, }, .destroy = _destroy, }, .from_attr = parse_selector(lib->settings->get_str(lib->settings, "%s.plugins.eap-radius.forward.ike_to_radius", "", lib->ns)), .to_attr = parse_selector(lib->settings->get_str(lib->settings, "%s.plugins.eap-radius.forward.radius_to_ike", "", lib->ns)), .from = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 8), .to = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 8), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), ); if (this->from_attr->get_count(this->from_attr) == 0 && this->to_attr->get_count(this->to_attr) == 0) { destroy(this); return NULL; } singleton = this; return &this->public; }