Forward specifcied RADIUS attributes between AAA backend and client
authorMartin Willi <martin@revosec.ch>
Fri, 24 Feb 2012 15:41:10 +0000 (16:41 +0100)
committerMartin Willi <martin@revosec.ch>
Mon, 5 Mar 2012 17:06:15 +0000 (18:06 +0100)
src/libcharon/plugins/eap_radius/Makefile.am
src/libcharon/plugins/eap_radius/eap_radius.c
src/libcharon/plugins/eap_radius/eap_radius_forward.c [new file with mode: 0644]
src/libcharon/plugins/eap_radius/eap_radius_forward.h [new file with mode: 0644]
src/libcharon/plugins/eap_radius/eap_radius_plugin.c

index 403f858..96bfa6f 100644 (file)
@@ -15,6 +15,7 @@ libstrongswan_eap_radius_la_SOURCES = \
        eap_radius.h eap_radius.c \
        eap_radius_accounting.h eap_radius_accounting.c \
        eap_radius_dae.h eap_radius_dae.c \
+       eap_radius_forward.h eap_radius_forward.c \
        radius_server.h radius_server.c \
        radius_socket.h radius_socket.c \
        radius_client.h radius_client.c \
index 3d8c823..d62bcd7 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include "eap_radius.h"
+#include "eap_radius_forward.h"
 
 #include "radius_message.h"
 #include "radius_client.h"
@@ -175,10 +176,12 @@ METHOD(eap_method_t, initiate, status_t,
        {
                add_eap_identity(this, request);
        }
+       eap_radius_forward_from_ike(request);
 
        response = this->client->request(this->client, request);
        if (response)
        {
+               eap_radius_forward_to_ike(response);
                if (radius2ike(this, response, out))
                {
                        status = NEED_MORE;
@@ -327,9 +330,11 @@ METHOD(eap_method_t, process, status_t,
        }
        request->add(request, RAT_EAP_MESSAGE, data);
 
+       eap_radius_forward_from_ike(request);
        response = this->client->request(this->client, request);
        if (response)
        {
+               eap_radius_forward_to_ike(response);
                switch (response->get_code(response))
                {
                        case RMC_ACCESS_CHALLENGE:
diff --git a/src/libcharon/plugins/eap_radius/eap_radius_forward.c b/src/libcharon/plugins/eap_radius/eap_radius_forward.c
new file mode 100644 (file)
index 0000000..cb4ca74
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <daemon.h>
+#include <utils/linked_list.h>
+#include <utils/hashtable.h>
+#include <threading/mutex.h>
+
+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)
+               {
+                       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)
+{
+       linked_list_t *queue;
+
+       if (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,
+                                               "charon.plugins.eap-radius.forward.ike_to_radius", "")),
+               .to_attr = parse_selector(lib->settings->get_str(lib->settings,
+                                               "charon.plugins.eap-radius.forward.radius_to_ike", "")),
+               .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;
+}
diff --git a/src/libcharon/plugins/eap_radius/eap_radius_forward.h b/src/libcharon/plugins/eap_radius/eap_radius_forward.h
new file mode 100644 (file)
index 0000000..e1a8c41
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup eap_radius_forward eap_radius_forward
+ * @{ @ingroup
+ */
+
+#ifndef EAP_RADIUS_FORWARD_H_
+#define EAP_RADIUS_FORWARD_H_
+
+#include "radius_message.h"
+
+#include <bus/listeners/listener.h>
+
+typedef struct eap_radius_forward_t eap_radius_forward_t;
+
+/**
+ * Forward RADIUS attributes in Notifies between client and AAA backend.
+ */
+struct eap_radius_forward_t {
+
+       /**
+        * Implements a listener.
+        */
+       listener_t listener;
+
+       /**
+        * Destroy a eap_radius_forward_t.
+        */
+       void (*destroy)(eap_radius_forward_t *this);
+};
+
+/**
+ * Create a eap_radius_forward instance.
+ */
+eap_radius_forward_t *eap_radius_forward_create();
+
+/**
+ * Forward RADIUS attributes from IKE notifies to a RADIUS request.
+ *
+ * @param request              RADIUS request message to add attributes to
+ */
+void eap_radius_forward_from_ike(radius_message_t *request);
+
+/**
+ * Forward RADIUS attributes from a RADIUS response to IKE notifies.
+ *
+ * @param response             RADIUS respose to read notifies from
+ */
+void eap_radius_forward_to_ike(radius_message_t *response);
+
+#endif /** EAP_RADIUS_FORWARD_H_ @}*/
index 62b53e5..e544aaf 100644 (file)
@@ -18,6 +18,7 @@
 #include "eap_radius.h"
 #include "eap_radius_accounting.h"
 #include "eap_radius_dae.h"
+#include "eap_radius_forward.h"
 #include "radius_client.h"
 #include "radius_server.h"
 
@@ -65,6 +66,11 @@ struct private_eap_radius_plugin_t {
         * Dynamic authorization extensions
         */
        eap_radius_dae_t *dae;
+
+       /**
+        * RADIUS <-> IKE attribute forwarding
+        */
+       eap_radius_forward_t *forward;
 };
 
 /**
@@ -194,6 +200,11 @@ METHOD(plugin_t, reload, bool,
 METHOD(plugin_t, destroy, void,
        private_eap_radius_plugin_t *this)
 {
+       if (this->forward)
+       {
+               charon->bus->remove_listener(charon->bus, &this->forward->listener);
+               this->forward->destroy(this->forward);
+       }
        DESTROY_IF(this->dae);
        this->servers->destroy_offset(this->servers,
                                                                  offsetof(radius_server_t, destroy));
@@ -223,6 +234,7 @@ plugin_t *eap_radius_plugin_create()
                .servers = linked_list_create(),
                .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
                .accounting = eap_radius_accounting_create(),
+               .forward = eap_radius_forward_create(),
        );
 
        load_servers(this);
@@ -238,6 +250,10 @@ plugin_t *eap_radius_plugin_create()
        {
                this->dae = eap_radius_dae_create(this->accounting);
        }
+       if (this->forward)
+       {
+               charon->bus->add_listener(charon->bus, &this->forward->listener);
+       }
 
        return &this->public.plugin;
 }