Implemented support for multiple RADIUS servers
authorMartin Willi <martin@revosec.ch>
Wed, 21 Jul 2010 15:06:00 +0000 (17:06 +0200)
committerMartin Willi <martin@revosec.ch>
Wed, 21 Jul 2010 15:25:09 +0000 (17:25 +0200)
src/libcharon/plugins/eap_radius/Makefile.am
src/libcharon/plugins/eap_radius/eap_radius.c
src/libcharon/plugins/eap_radius/eap_radius_plugin.c
src/libcharon/plugins/eap_radius/eap_radius_plugin.h
src/libcharon/plugins/eap_radius/radius_client.c
src/libcharon/plugins/eap_radius/radius_client.h
src/libcharon/plugins/eap_radius/radius_server.c [new file with mode: 0644]
src/libcharon/plugins/eap_radius/radius_server.h [new file with mode: 0644]
src/libcharon/plugins/eap_radius/radius_socket.c [new file with mode: 0644]
src/libcharon/plugins/eap_radius/radius_socket.h [new file with mode: 0644]

index a3abd41..afc50bc 100644 (file)
@@ -13,6 +13,8 @@ endif
 libstrongswan_eap_radius_la_SOURCES = \
        eap_radius_plugin.h eap_radius_plugin.c \
        eap_radius.h eap_radius.c \
+       radius_server.h radius_server.c \
+       radius_socket.h radius_socket.c \
        radius_client.h radius_client.c \
        radius_message.h radius_message.c
 
index 3038e87..65b868b 100644 (file)
@@ -53,11 +53,6 @@ struct private_eap_radius_t {
        u_int32_t vendor;
 
        /**
-        * EAP MSK, if method established one
-        */
-       chunk_t msk;
-
-       /**
         * RADIUS client instance
         */
        radius_client_t *client;
@@ -248,8 +243,6 @@ METHOD(eap_method_t, process, status_t,
                                status = FAILED;
                                break;
                        case RMC_ACCESS_ACCEPT:
-                               this->msk = this->client->decrypt_msk(this->client,
-                                                                                                         response, request);
                                if (this->class_group)
                                {
                                        process_class(this, response);
@@ -277,11 +270,14 @@ METHOD(eap_method_t, get_type, eap_type_t,
 }
 
 METHOD(eap_method_t, get_msk, status_t,
-       private_eap_radius_t *this, chunk_t *msk)
+       private_eap_radius_t *this, chunk_t *out)
 {
-       if (this->msk.ptr)
+       chunk_t msk;
+
+       msk = this->client->get_msk(this->client);
+       if (msk.len)
        {
-               *msk = this->msk;
+               *out = msk;
                return SUCCESS;
        }
        return FAILED;
@@ -306,7 +302,6 @@ METHOD(eap_method_t, destroy, void,
        this->peer->destroy(this->peer);
        this->server->destroy(this->server);
        this->client->destroy(this->client);
-       chunk_clear(&this->msk);
        free(this);
 }
 
index e194077..91aae2f 100644 (file)
 
 #include "eap_radius.h"
 #include "radius_client.h"
+#include "radius_server.h"
 
 #include <daemon.h>
 
+/**
+ * Default RADIUS server port, when not configured
+ */
+#define RADIUS_PORT 1812
+
+typedef struct private_eap_radius_plugin_t private_eap_radius_plugin_t;
+
+/**
+ * Private data of an eap_radius_plugin_t object.
+ */
+struct private_eap_radius_plugin_t {
+
+       /**
+        * Public radius_plugin_t interface.
+        */
+       eap_radius_plugin_t public;
+
+       /**
+        * List of RADIUS servers
+        */
+       linked_list_t *servers;
+};
+
+/**
+ * Instance of the EAP plugin
+ */
+static private_eap_radius_plugin_t *instance = NULL;
+
 METHOD(plugin_t, destroy, void,
-       eap_radius_plugin_t *this)
+       private_eap_radius_plugin_t *this)
 {
        charon->eap->remove_method(charon->eap, (eap_constructor_t)eap_radius_create);
-       radius_client_cleanup();
+       this->servers->destroy_offset(this->servers,
+                                                                 offsetof(radius_server_t, destroy));
        free(this);
+       instance = NULL;
 }
 
-/*
- * see header file
+/**
+ * Load RADIUS servers from configuration
  */
-plugin_t *eap_radius_plugin_create()
+static bool load_servers(private_eap_radius_plugin_t *this)
 {
-       eap_radius_plugin_t *this;
+       enumerator_t *enumerator;
+       radius_server_t *server;
+       char *nas_identifier, *secret, *address, *section;
+       int port, sockets, preference;
+
+       address = lib->settings->get_str(lib->settings,
+                                       "charon.plugins.eap-radius.server", NULL);
+       if (address)
+       {       /* legacy configuration */
+               secret = lib->settings->get_str(lib->settings,
+                                       "charon.plugins.eap-radius.secret", NULL);
+               if (!secret)
+               {
+                       DBG1(DBG_CFG, "no RADUIS secret defined");
+                       return FALSE;
+               }
+               nas_identifier = lib->settings->get_str(lib->settings,
+                                       "charon.plugins.eap-radius.nas_identifier", "strongSwan");
+               port = lib->settings->get_int(lib->settings,
+                                       "charon.plugins.eap-radius.port", RADIUS_PORT);
+               sockets = lib->settings->get_int(lib->settings,
+                                       "charon.plugins.eap-radius.sockets", 1);
+               server = radius_server_create(address, port, nas_identifier,
+                                                                         secret, sockets, 0);
+               if (!server)
+               {
+                       DBG1(DBG_CFG, "no RADUIS server defined");
+                       return FALSE;
+               }
+               this->servers->insert_last(this->servers, server);
+               return TRUE;
+       }
 
-       if (!radius_client_init())
+       enumerator = lib->settings->create_section_enumerator(lib->settings,
+                                                                               "charon.plugins.eap-radius.servers");
+       while (enumerator->enumerate(enumerator, &section))
        {
-               DBG1(DBG_CFG, "RADIUS plugin initialization failed");
-               return NULL;
+               address = lib->settings->get_str(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.address", NULL, section);
+               if (!address)
+               {
+                       DBG1(DBG_CFG, "RADIUS server '%s' misses address, skipped", section);
+                       continue;
+               }
+               secret = lib->settings->get_str(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.secret", NULL, section);
+               if (!secret)
+               {
+                       DBG1(DBG_CFG, "RADIUS server '%s' misses secret, skipped", section);
+                       continue;
+               }
+               nas_identifier = lib->settings->get_str(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.nas_identifier",
+                       "strongSwan", section);
+               port = lib->settings->get_int(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.port", RADIUS_PORT, section);
+               sockets = lib->settings->get_int(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.sockets", 1, section);
+               preference = lib->settings->get_int(lib->settings,
+                       "charon.plugins.eap-radius.servers.%s.preference", 0, section);
+               server = radius_server_create(address, port, nas_identifier,
+                                                                         secret, sockets, preference);
+               if (!server)
+               {
+                       DBG1(DBG_CFG, "loading RADIUS server '%s' failed, skipped", section);
+                       continue;
+               }
+               this->servers->insert_last(this->servers, server);
        }
+       enumerator->destroy(enumerator);
+
+       if (this->servers->get_count(this->servers) == 0)
+       {
+               DBG1(DBG_CFG, "no valid RADIUS server configuration found");
+               return FALSE;
+       }
+       return TRUE;
+}
+
+/*
+ * see header file
+ */
+plugin_t *eap_radius_plugin_create()
+{
+       private_eap_radius_plugin_t *this;
 
        INIT(this,
-               .plugin.destroy = _destroy,
+               .public.plugin.destroy = _destroy,
+               .servers = linked_list_create(),
        );
 
+       if (!load_servers(this))
+       {
+               destroy(this);
+               return NULL;
+       }
        charon->eap->add_method(charon->eap, EAP_RADIUS, 0,
                                                        EAP_SERVER, (eap_constructor_t)eap_radius_create);
 
-       return &this->plugin;
+       instance = this;
+
+       return &this->public.plugin;
+}
+
+/**
+ * See header
+ */
+enumerator_t *eap_radius_create_server_enumerator()
+{
+       if (instance)
+       {
+               return instance->servers->create_enumerator(instance->servers);
+       }
+       return enumerator_create_empty();
 }
 
index f2b8b50..cb72436 100644 (file)
@@ -25,6 +25,7 @@
 #define EAP_RADIUS_PLUGIN_H_
 
 #include <plugins/plugin.h>
+#include <utils/enumerator.h>
 
 typedef struct eap_radius_plugin_t eap_radius_plugin_t;
 
@@ -42,4 +43,11 @@ struct eap_radius_plugin_t {
        plugin_t plugin;
 };
 
+/**
+ * Create an enumerator over all loaded RADIUS servers.
+ *
+ * @return                     enumerator over radius_server_t
+ */
+enumerator_t *eap_radius_create_server_enumerator();
+
 #endif /** EAP_RADIUS_PLUGIN_H_ @}*/
index 0727bc0..232b913 100644 (file)
@@ -15,6 +15,9 @@
 
 #include "radius_client.h"
 
+#include "eap_radius_plugin.h"
+#include "radius_server.h"
+
 #include <unistd.h>
 #include <errno.h>
 
 #include <threading/condvar.h>
 #include <threading/mutex.h>
 
-/**
- * Default RADIUS server port, when not configured
- */
-#define RADIUS_PORT 1812
-
-/**
- * Vendor-Id of Microsoft specific attributes
- */
-#define VENDOR_ID_MICROSOFT 311
-
-/**
- * Microsoft specific vendor attributes
- */
-#define MS_MPPE_SEND_KEY 16
-#define MS_MPPE_RECV_KEY 17
-
 typedef struct private_radius_client_t private_radius_client_t;
 
-typedef struct entry_t entry_t;
-
-/**
- * A socket pool entry.
- */
-struct entry_t {
-       /** socket file descriptor */
-       int fd;
-       /** current RADIUS identifier */
-       u_int8_t identifier;
-       /** hasher to use for response verification */
-       hasher_t *hasher;
-       /** HMAC-MD5 signer to build Message-Authenticator attribute */
-       signer_t *signer;
-       /** random number generator for RADIUS request authenticator */
-       rng_t *rng;
-};
-
 /**
  * Private data of an radius_client_t object.
  */
@@ -71,170 +40,20 @@ struct private_radius_client_t {
        radius_client_t public;
 
        /**
+        * Selected RADIUS server
+        */
+       radius_server_t *server;
+
+       /**
         * RADIUS servers State attribute
         */
        chunk_t state;
-};
-
-/**
- * Global list of radius sockets, contains entry_t's
- */
-static linked_list_t *sockets;
-
-/**
- * mutex to lock sockets list
- */
-static mutex_t *mutex;
-
-/**
- * condvar to wait for sockets
- */
-static condvar_t *condvar;
-
-/**
- * RADIUS secret
- */
-static chunk_t secret;
-
-/**
- * NAS-Identifier
- */
-static chunk_t nas_identifier;
-
-/**
- * Clean up socket list
- */
-void radius_client_cleanup()
-{
-       entry_t *entry;
-
-       mutex->destroy(mutex);
-       condvar->destroy(condvar);
-       while (sockets->remove_last(sockets, (void**)&entry) == SUCCESS)
-       {
-               entry->rng->destroy(entry->rng);
-               entry->hasher->destroy(entry->hasher);
-               entry->signer->destroy(entry->signer);
-               close(entry->fd);
-               free(entry);
-       }
-       sockets->destroy(sockets);
-}
-
-/**
- * Initialize the socket list
- */
-bool radius_client_init()
-{
-       int i, count, fd;
-       u_int16_t port;
-       entry_t *entry;
-       host_t *host;
-       char *server;
-
-       nas_identifier.ptr = lib->settings->get_str(lib->settings,
-                                       "charon.plugins.eap-radius.nas_identifier", "strongSwan");
-       nas_identifier.len = strlen(nas_identifier.ptr);
-
-       secret.ptr = lib->settings->get_str(lib->settings,
-                                       "charon.plugins.eap-radius.secret", NULL);
-       if (!secret.ptr)
-       {
-               DBG1(DBG_CFG, "no RADUIS secret defined");
-               return FALSE;
-       }
-       secret.len = strlen(secret.ptr);
-       server = lib->settings->get_str(lib->settings,
-                                       "charon.plugins.eap-radius.server", NULL);
-       if (!server)
-       {
-               DBG1(DBG_CFG, "no RADUIS server defined");
-               return FALSE;
-       }
-       port = lib->settings->get_int(lib->settings,
-                                       "charon.plugins.eap-radius.port", RADIUS_PORT);
-       host = host_create_from_dns(server, 0, port);
-       if (!host)
-       {
-               return FALSE;
-       }
-       count = lib->settings->get_int(lib->settings,
-                                       "charon.plugins.eap-radius.sockets", 1);
-
-       sockets = linked_list_create();
-       mutex = mutex_create(MUTEX_TYPE_DEFAULT);
-       condvar = condvar_create(CONDVAR_TYPE_DEFAULT);
-       for (i = 0; i < count; i++)
-       {
-               fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP);
-               if (fd < 0)
-               {
-                       DBG1(DBG_CFG, "opening RADIUS socket failed");
-                       host->destroy(host);
-                       radius_client_cleanup();
-                       return FALSE;
-               }
-               if (connect(fd, host->get_sockaddr(host),
-                                       *host->get_sockaddr_len(host)) < 0)
-               {
-                       DBG1(DBG_CFG, "connecting RADIUS socket failed");
-                       host->destroy(host);
-                       radius_client_cleanup();
-                       return FALSE;
-               }
-               entry = malloc_thing(entry_t);
-               entry->fd = fd;
-               /* we use per-socket crypto elements: this reduces overhead, but
-                * is still thread-save. */
-               entry->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
-               entry->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);
-               entry->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
-               if (!entry->hasher || !entry->signer || !entry->rng)
-               {
-                       DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
-                       DESTROY_IF(entry->hasher);
-                       DESTROY_IF(entry->signer);
-                       DESTROY_IF(entry->rng);
-                       free(entry);
-                       host->destroy(host);
-                       radius_client_cleanup();
-                       return FALSE;
-               }
-               entry->signer->set_key(entry->signer, secret);
-               /* we use a random identifier, helps if we restart often (testing) */
-               entry->identifier = random();
-               sockets->insert_last(sockets, entry);
-       }
-       host->destroy(host);
-       return TRUE;
-}
-
-/**
- * Get a socket from the pool, block if none available
- */
-static entry_t* get_socket()
-{
-       entry_t *entry;
 
-       mutex->lock(mutex);
-       while (sockets->remove_first(sockets, (void**)&entry) != SUCCESS)
-       {
-               condvar->wait(condvar, mutex);
-       }
-       mutex->unlock(mutex);
-       return entry;
-}
-
-/**
- * Release a socket to the pool
- */
-static void put_socket(entry_t *entry)
-{
-       mutex->lock(mutex);
-       sockets->insert_last(sockets, entry);
-       mutex->unlock(mutex);
-       condvar->signal(condvar);
-}
+       /**
+        * EAP MSK, from MPPE keys
+        */
+       chunk_t msk;
+};
 
 /**
  * Save the state attribute to include in further request
@@ -265,207 +84,51 @@ METHOD(radius_client_t, request, radius_message_t*,
        private_radius_client_t *this, radius_message_t *req)
 {
        char virtual[] = {0x00,0x00,0x00,0x05};
-       entry_t *socket;
-       chunk_t data;
-       int i;
-
-       socket = get_socket();
+       radius_socket_t *socket;
+       radius_message_t *res;
 
-       /* set Message Identifier */
-       req->set_identifier(req, socket->identifier++);
        /* we add the "Virtual" NAS-Port-Type, as we SHOULD include one */
        req->add(req, RAT_NAS_PORT_TYPE, chunk_create(virtual, sizeof(virtual)));
        /* add our NAS-Identifier */
-       req->add(req, RAT_NAS_IDENTIFIER, nas_identifier);
+       req->add(req, RAT_NAS_IDENTIFIER,
+                        this->server->get_nas_identifier(this->server));
        /* add State attribute, if server sent one */
        if (this->state.ptr)
        {
                req->add(req, RAT_STATE, this->state);
        }
-       /* sign the request */
-       req->sign(req, socket->rng, socket->signer);
-
-       data = req->get_encoding(req);
-       /* timeout after 2, 3, 4, 5 seconds */
-       for (i = 2; i <= 5; i++)
+       socket = this->server->get_socket(this->server);
+       DBG1(DBG_CFG, "sending RADIUS %N to %#H", radius_message_code_names,
+                req->get_code(req), this->server->get_address(this->server));
+       res = socket->request(socket, req);
+       if (res)
        {
-               radius_message_t *response;
-               bool retransmit = FALSE;
-               struct timeval tv;
-               char buf[4096];
-               fd_set fds;
-               int res;
-
-               if (send(socket->fd, data.ptr, data.len, 0) != data.len)
-               {
-                       DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
-                       put_socket(socket);
-                       return NULL;
-               }
-               tv.tv_sec = i;
-               tv.tv_usec = 0;
-
-               while (TRUE)
-               {
-                       FD_ZERO(&fds);
-                       FD_SET(socket->fd, &fds);
-                       res = select(socket->fd + 1, &fds, NULL, NULL, &tv);
-                       /* TODO: updated tv to time not waited. Linux does this for us. */
-                       if (res < 0)
-                       {       /* failed */
-                               DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
-                                        strerror(errno));
-                               break;
-                       }
-                       if (res == 0)
-                       {       /* timeout */
-                               DBG1(DBG_CFG, "retransmitting RADIUS message");
-                               retransmit = TRUE;
-                               break;
-                       }
-                       res = recv(socket->fd, buf, sizeof(buf), MSG_DONTWAIT);
-                       if (res <= 0)
-                       {
-                               DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
-                                        strerror(errno));
-                               break;
-                       }
-                       response = radius_message_parse_response(chunk_create(buf, res));
-                       if (response)
-                       {
-                               if (response->verify(response, req->get_authenticator(req),
-                                                       secret, socket->hasher, socket->signer))
-                               {
-                                       save_state(this, response);
-                                       put_socket(socket);
-                                       return response;
-                               }
-                               response->destroy(response);
-                       }
-                       DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
-               }
-               if (!retransmit)
+               DBG1(DBG_CFG, "received RADIUS %N from %#H", radius_message_code_names,
+                        res->get_code(res), this->server->get_address(this->server));
+               save_state(this, res);
+               if (res->get_code(res) == RMC_ACCESS_ACCEPT)
                {
-                       break;
+                       chunk_clear(&this->msk);
+                       this->msk = socket->decrypt_msk(socket, req, res);
                }
+               this->server->put_socket(this->server, socket, TRUE);
+               return res;
        }
-       DBG1(DBG_CFG, "RADIUS server is not responding");
-       put_socket(socket);
+       this->server->put_socket(this->server, socket, FALSE);
        charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING);
        return NULL;
 }
 
-/**
- * Decrypt a MS-MPPE-Send/Recv-Key
- */
-static chunk_t decrypt_mppe_key(private_radius_client_t *this, u_int16_t salt,
-                                                               chunk_t C, radius_message_t *request)
-{
-       chunk_t A, R, P, seed;
-       u_char *c, *p;
-       hasher_t *hasher;
-
-       /**
-        * From RFC2548 (encryption):
-        * b(1) = MD5(S + R + A)    c(1) = p(1) xor b(1)   C = c(1)
-        * b(2) = MD5(S + c(1))     c(2) = p(2) xor b(2)   C = C + c(2)
-        *      . . .
-        * b(i) = MD5(S + c(i-1))   c(i) = p(i) xor b(i)   C = C + c(i)
-        */
-
-       if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
-       {
-               return chunk_empty;
-       }
-
-       hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
-       if (!hasher)
-       {
-               return chunk_empty;
-       }
-
-       A = chunk_create((u_char*)&salt, sizeof(salt));
-       R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
-       P = chunk_alloca(C.len);
-       p = P.ptr;
-       c = C.ptr;
-
-       seed = chunk_cata("cc", R, A);
-
-       while (c < C.ptr + C.len)
-       {
-               /* b(i) = MD5(S + c(i-1)) */
-               hasher->get_hash(hasher, secret, NULL);
-               hasher->get_hash(hasher, seed, p);
-
-               /* p(i) = b(i) xor c(1) */
-               memxor(p, c, HASH_SIZE_MD5);
-
-               /* prepare next round */
-               seed = chunk_create(c, HASH_SIZE_MD5);
-               c += HASH_SIZE_MD5;
-               p += HASH_SIZE_MD5;
-       }
-       hasher->destroy(hasher);
-
-       /* remove truncation, first byte is key length */
-       if (*P.ptr >= P.len)
-       {       /* decryption failed? */
-               return chunk_empty;
-       }
-       return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
-}
-
-METHOD(radius_client_t, decrypt_msk, chunk_t,
-       private_radius_client_t *this, radius_message_t *response,
-       radius_message_t *request)
+METHOD(radius_client_t, get_msk, chunk_t,
+       private_radius_client_t *this)
 {
-       struct {
-               u_int32_t id;
-               u_int8_t type;
-               u_int8_t length;
-               u_int16_t salt;
-               u_int8_t key[];
-       } __attribute__((packed)) *mppe_key;
-       enumerator_t *enumerator;
-       chunk_t data, send = chunk_empty, recv = chunk_empty;
-       int type;
-
-       enumerator = response->create_enumerator(response);
-       while (enumerator->enumerate(enumerator, &type, &data))
-       {
-               if (type == RAT_VENDOR_SPECIFIC &&
-                       data.len > sizeof(*mppe_key))
-               {
-                       mppe_key = (void*)data.ptr;
-                       if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT &&
-                               mppe_key->length == data.len - sizeof(mppe_key->id))
-                       {
-                               data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key));
-                               if (mppe_key->type == MS_MPPE_SEND_KEY)
-                               {
-                                       send = decrypt_mppe_key(this, mppe_key->salt, data, request);
-                               }
-                               if (mppe_key->type == MS_MPPE_RECV_KEY)
-                               {
-                                       recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
-                               }
-                       }
-               }
-       }
-       enumerator->destroy(enumerator);
-       if (send.ptr && recv.ptr)
-       {
-               return chunk_cat("mm", recv, send);
-       }
-       chunk_clear(&send);
-       chunk_clear(&recv);
-       return chunk_empty;
+       return this->msk;
 }
 
 METHOD(radius_client_t, destroy, void,
        private_radius_client_t *this)
 {
+       chunk_clear(&this->msk);
        free(this->state.ptr);
        free(this);
 }
@@ -476,15 +139,45 @@ METHOD(radius_client_t, destroy, void,
 radius_client_t *radius_client_create()
 {
        private_radius_client_t *this;
+       enumerator_t *enumerator;
+       radius_server_t *server;
+       int current, best = -1;
 
        INIT(this,
                .public = {
                        .request = _request,
-                       .decrypt_msk = _decrypt_msk,
+                       .get_msk = _get_msk,
                        .destroy = _destroy,
                },
        );
 
+       enumerator = eap_radius_create_server_enumerator();
+       while (enumerator->enumerate(enumerator, &server))
+       {
+               current = server->get_preference(server);
+               if (current > best ||
+                       /* for two with equal preference, 50-50 chance */
+                       (current == best && random() % 2 == 0))
+               {
+                       DBG2(DBG_CFG, "RADIUS server %H is candidate: %d",
+                                server->get_address(server), current);
+                       best = current;
+                       this->server = server;
+               }
+               else
+               {
+                       DBG2(DBG_CFG, "RADIUS server %H skipped: %d",
+                                server->get_address(server), current);
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       if (!this->server)
+       {
+               free(this);
+               return NULL;
+       }
+
        return &this->public;
 }
 
index 77ba948..e4f3a72 100644 (file)
@@ -29,19 +29,14 @@ typedef struct radius_client_t radius_client_t;
  * RADIUS client functionality.
  *
  * To communicate with a RADIUS server, create a client and send messages over
- * it. All instances share a fixed size pool of sockets. The client reserves
- * a socket during request() and releases it afterwards.
+ * it. The client allocates a socket from the best RADIUS server abailable.
  */
 struct radius_client_t {
 
        /**
         * Send a RADIUS request and wait for the response.
         *
-        * The client fills in RADIUS Message identifier, NAS-Identifier,
-        * NAS-Port-Type, builds a Request-Authenticator and calculates the
-        * Message-Authenticator attribute.
-        * The received response gets verified using the Response-Identifier
-        * and the Message-Authenticator attribute.
+        * The client fills in NAS-Identifier nad NAS-Port-Type
         *
         * @param msg                   RADIUS request message to send
         * @return                              response, NULL if timed out/verification failed
@@ -49,14 +44,11 @@ struct radius_client_t {
        radius_message_t* (*request)(radius_client_t *this, radius_message_t *msg);
 
        /**
-        * Decrypt the MSK encoded in a messages MS-MPPE-Send/Recv-Key.
+        * Get the EAP MSK after successful RADIUS authentication.
         *
-        * @param response              RADIUS response message containing attributes
-        * @param request               associated RADIUS request message
-        * @return                              allocated MSK, empty chunk if none found
+        * @return                              MSK, allocated
         */
-       chunk_t (*decrypt_msk)(radius_client_t *this, radius_message_t *response,
-                                                  radius_message_t *request);
+       chunk_t (*get_msk)(radius_client_t *this);
 
        /**
         * Destroy the client, release the socket.
@@ -65,24 +57,10 @@ struct radius_client_t {
 };
 
 /**
- * Create a RADIUS client, acquire a socket.
- *
- * This call might block if the socket pool is empty.
+ * Create a RADIUS client.
  *
  * @return                     radius_client_t object
  */
 radius_client_t *radius_client_create();
 
-/**
- * Initialize the socket pool.
- *
- * @return                     TRUE if initialization successful
- */
-bool radius_client_init();
-
-/**
- * Cleanup the socket pool.
- */
-void radius_client_cleanup();
-
 #endif /** RADIUS_CLIENT_H_ @}*/
diff --git a/src/libcharon/plugins/eap_radius/radius_server.c b/src/libcharon/plugins/eap_radius/radius_server.c
new file mode 100644 (file)
index 0000000..f54b8b2
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 "radius_server.h"
+
+#include <threading/mutex.h>
+#include <threading/condvar.h>
+#include <utils/linked_list.h>
+
+typedef struct private_radius_server_t private_radius_server_t;
+
+/**
+ * Private data of an radius_server_t object.
+ */
+struct private_radius_server_t {
+
+       /**
+        * Public radius_server_t interface.
+        */
+       radius_server_t public;
+
+       /**
+        * RADIUS server address
+        */
+       host_t *host;
+
+       /**
+        * list of radius sockets, as radius_socket_t
+        */
+       linked_list_t *sockets;
+
+       /**
+        * Total number of sockets, in list + currently in use
+        */
+       int socket_count;
+
+       /**
+        * mutex to lock sockets list
+        */
+       mutex_t *mutex;
+
+       /**
+        * condvar to wait for sockets
+        */
+       condvar_t *condvar;
+
+       /**
+        * RADIUS secret
+        */
+       chunk_t secret;
+
+       /**
+        * NAS-Identifier
+        */
+       chunk_t nas_identifier;
+
+       /**
+        * Preference boost for this server
+        */
+       int preference;
+
+       /**
+        * Is the server currently reachable
+        */
+       bool reachable;
+
+       /**
+        * Retry counter for unreachable servers
+        */
+       int retry;
+};
+
+METHOD(radius_server_t, get_socket, radius_socket_t*,
+       private_radius_server_t *this)
+{
+       radius_socket_t *skt;
+
+       this->mutex->lock(this->mutex);
+       while (this->sockets->remove_first(this->sockets, (void**)&skt) != SUCCESS)
+       {
+               this->condvar->wait(this->condvar, this->mutex);
+       }
+       this->mutex->unlock(this->mutex);
+       return skt;
+}
+
+METHOD(radius_server_t, put_socket, void,
+       private_radius_server_t *this, radius_socket_t *skt, bool result)
+{
+       this->mutex->lock(this->mutex);
+       this->sockets->insert_last(this->sockets, skt);
+       this->mutex->unlock(this->mutex);
+       this->condvar->signal(this->condvar);
+       this->reachable = result;
+}
+
+METHOD(radius_server_t, get_nas_identifier, chunk_t,
+       private_radius_server_t *this)
+{
+       return this->nas_identifier;
+}
+
+METHOD(radius_server_t, get_preference, int,
+       private_radius_server_t *this)
+{
+       int pref;
+
+       if (this->socket_count == 0)
+       {       /* don't have sockets, huh? */
+               return -1;
+       }
+       /* calculate preference between 0-100 + boost */
+       pref = this->preference;
+       pref += this->sockets->get_count(this->sockets) * 100 / this->socket_count;
+       if (this->reachable)
+       {       /* reachable server get a boost: pref = 110-210 + boost */
+               return pref + 110;
+       }
+       /* Not reachable. Increase preference randomly to let it retry from
+        * time to time, especially if other servers have high load. */
+       this->retry++;
+       if (this->retry % 128 == 0)
+       {       /* every 64th request gets 210, same as unloaded reachable */
+               return pref + 110;
+       }
+       if (this->retry % 32 == 0)
+       {       /* every 32th request gets 190, wins against average loaded */
+               return pref + 90;
+       }
+       if (this->retry % 8 == 0)
+       {       /* every 8th request gets 110, same as server under load */
+               return pref + 10;
+       }
+       /* other get ~100, less than fully loaded */
+       return pref;
+}
+
+METHOD(radius_server_t, get_address, host_t*,
+       private_radius_server_t *this)
+{
+       return this->host;
+}
+
+METHOD(radius_server_t, destroy, void,
+       private_radius_server_t *this)
+{
+       DESTROY_IF(this->host);
+       this->mutex->destroy(this->mutex);
+       this->condvar->destroy(this->condvar);
+       this->sockets->destroy_offset(this->sockets,
+                                                                 offsetof(radius_socket_t, destroy));
+       free(this);
+}
+
+/**
+ * See header
+ */
+radius_server_t *radius_server_create(char *server, u_int16_t port,
+                               char *nas_identifier, char *secret, int sockets, int preference)
+{
+       private_radius_server_t *this;
+       radius_socket_t *socket;
+
+       INIT(this,
+               .public = {
+                       .get_socket = _get_socket,
+                       .put_socket = _put_socket,
+                       .get_nas_identifier = _get_nas_identifier,
+                       .get_preference = _get_preference,
+                       .get_address = _get_address,
+                       .destroy = _destroy,
+               },
+               .reachable = TRUE,
+               .nas_identifier = chunk_create(nas_identifier, strlen(nas_identifier)),
+               .socket_count = sockets,
+               .sockets = linked_list_create(),
+               .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
+               .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
+               .host = host_create_from_dns(server, 0, port),
+               .preference = preference,
+       );
+
+       if (!this->host)
+       {
+               destroy(this);
+               return NULL;
+       }
+       while (sockets--)
+       {
+               socket = radius_socket_create(this->host,
+                                                                         chunk_create(secret, strlen(secret)));
+               if (!socket)
+               {
+                       destroy(this);
+                       return NULL;
+               }
+               this->sockets->insert_last(this->sockets, socket);
+       }
+       return &this->public;
+}
diff --git a/src/libcharon/plugins/eap_radius/radius_server.h b/src/libcharon/plugins/eap_radius/radius_server.h
new file mode 100644 (file)
index 0000000..b820cb5
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 radius_server radius_server
+ * @{ @ingroup eap_radius
+ */
+
+#ifndef RADIUS_SERVER_H_
+#define RADIUS_SERVER_H_
+
+typedef struct radius_server_t radius_server_t;
+
+#include "radius_socket.h"
+
+/**
+ * RADIUS server configuration.
+ */
+struct radius_server_t {
+
+       /**
+        * Get a RADIUS socket from the pool to communicate with this server.
+        *
+        * @return                      RADIUS socket
+        */
+       radius_socket_t* (*get_socket)(radius_server_t *this);
+
+       /**
+        * Release a socket to the pool after use.
+        *
+        * @param skt           RADIUS socket to release
+        * @param result        result of the socket use, TRUE for success
+        */
+       void (*put_socket)(radius_server_t *this, radius_socket_t *skt, bool result);
+
+       /**
+        * Get the NAS-Identifier to use with this server.
+        *
+        * @return                      NAS-Identifier, internal data
+        */
+       chunk_t (*get_nas_identifier)(radius_server_t *this);
+
+       /**
+        * Get the preference of this server.
+        *
+        * Based on the available sockets and the server reachability a preference
+        * value is calculated: better servers return a higher value.
+        */
+       int (*get_preference)(radius_server_t *this);
+
+       /**
+        * Get the address of the RADIUS server.
+        *
+        * @return                      address, internal data
+        */
+       host_t* (*get_address)(radius_server_t *this);
+
+       /**
+        * Destroy a radius_server_t.
+        */
+       void (*destroy)(radius_server_t *this);
+};
+
+/**
+ * Create a radius_server instance.
+ *
+ * @param server                       server address
+ * @param port                         server port
+ * @param nas_identifier       NAS-Identifier to use with this server
+ * @param sockets                      number of sockets to create in pool
+ * @param preference           preference boost for this server
+ */
+radius_server_t *radius_server_create(char *server, u_int16_t port,
+                       char *nas_identifier, char *secret, int sockets, int preference);
+
+#endif /** RADIUS_SERVER_H_ @}*/
diff --git a/src/libcharon/plugins/eap_radius/radius_socket.c b/src/libcharon/plugins/eap_radius/radius_socket.c
new file mode 100644 (file)
index 0000000..f46c27e
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 "radius_socket.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <debug.h>
+
+/**
+ * Vendor-Id of Microsoft specific attributes
+ */
+#define VENDOR_ID_MICROSOFT 311
+
+/**
+ * Microsoft specific vendor attributes
+ */
+#define MS_MPPE_SEND_KEY 16
+#define MS_MPPE_RECV_KEY 17
+
+typedef struct private_radius_socket_t private_radius_socket_t;
+
+/**
+ * Private data of an radius_socket_t object.
+ */
+struct private_radius_socket_t {
+
+       /**
+        * Public radius_socket_t interface.
+        */
+       radius_socket_t public;
+
+       /**
+        * socket file descriptor
+        */
+       int fd;
+
+       /**
+        * current RADIUS identifier
+        */
+       u_int8_t identifier;
+
+       /**
+        * hasher to use for response verification
+        */
+       hasher_t *hasher;
+
+       /**
+        * HMAC-MD5 signer to build Message-Authenticator attribute
+        */
+       signer_t *signer;
+
+       /**
+        * random number generator for RADIUS request authenticator
+        */
+       rng_t *rng;
+
+       /**
+        * RADIUS secret
+        */
+       chunk_t secret;
+};
+
+METHOD(radius_socket_t, request, radius_message_t*,
+       private_radius_socket_t *this, radius_message_t *request)
+{
+       chunk_t data;
+       int i;
+
+       /* set Message Identifier */
+       request->set_identifier(request, this->identifier++);
+       /* sign the request */
+       request->sign(request, this->rng, this->signer);
+
+       data = request->get_encoding(request);
+       /* timeout after 2, 3, 4, 5 seconds */
+       for (i = 2; i <= 5; i++)
+       {
+               radius_message_t *response;
+               bool retransmit = FALSE;
+               struct timeval tv;
+               char buf[4096];
+               fd_set fds;
+               int res;
+
+               if (send(this->fd, data.ptr, data.len, 0) != data.len)
+               {
+                       DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
+                       return NULL;
+               }
+               tv.tv_sec = i;
+               tv.tv_usec = 0;
+
+               while (TRUE)
+               {
+                       FD_ZERO(&fds);
+                       FD_SET(this->fd, &fds);
+                       res = select(this->fd + 1, &fds, NULL, NULL, &tv);
+                       /* TODO: updated tv to time not waited. Linux does this for us. */
+                       if (res < 0)
+                       {       /* failed */
+                               DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
+                                        strerror(errno));
+                               break;
+                       }
+                       if (res == 0)
+                       {       /* timeout */
+                               DBG1(DBG_CFG, "retransmitting RADIUS message");
+                               retransmit = TRUE;
+                               break;
+                       }
+                       res = recv(this->fd, buf, sizeof(buf), MSG_DONTWAIT);
+                       if (res <= 0)
+                       {
+                               DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
+                                        strerror(errno));
+                               break;
+                       }
+                       response = radius_message_parse_response(chunk_create(buf, res));
+                       if (response)
+                       {
+                               if (response->verify(response,
+                                                       request->get_authenticator(request), this->secret,
+                                                       this->hasher, this->signer))
+                               {
+                                       return response;
+                               }
+                               response->destroy(response);
+                       }
+                       DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
+               }
+               if (!retransmit)
+               {
+                       break;
+               }
+       }
+       DBG1(DBG_CFG, "RADIUS server is not responding");
+       return NULL;
+}
+
+/**
+ * Decrypt a MS-MPPE-Send/Recv-Key
+ */
+static chunk_t decrypt_mppe_key(private_radius_socket_t *this, u_int16_t salt,
+                                                               chunk_t C, radius_message_t *request)
+{
+       chunk_t A, R, P, seed;
+       u_char *c, *p;
+
+       /**
+        * From RFC2548 (encryption):
+        * b(1) = MD5(S + R + A)    c(1) = p(1) xor b(1)   C = c(1)
+        * b(2) = MD5(S + c(1))     c(2) = p(2) xor b(2)   C = C + c(2)
+        *      . . .
+        * b(i) = MD5(S + c(i-1))   c(i) = p(i) xor b(i)   C = C + c(i)
+        */
+
+       if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
+       {
+               return chunk_empty;
+       }
+
+       A = chunk_create((u_char*)&salt, sizeof(salt));
+       R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
+       P = chunk_alloca(C.len);
+       p = P.ptr;
+       c = C.ptr;
+
+       seed = chunk_cata("cc", R, A);
+
+       while (c < C.ptr + C.len)
+       {
+               /* b(i) = MD5(S + c(i-1)) */
+               this->hasher->get_hash(this->hasher, this->secret, NULL);
+               this->hasher->get_hash(this->hasher, seed, p);
+
+               /* p(i) = b(i) xor c(1) */
+               memxor(p, c, HASH_SIZE_MD5);
+
+               /* prepare next round */
+               seed = chunk_create(c, HASH_SIZE_MD5);
+               c += HASH_SIZE_MD5;
+               p += HASH_SIZE_MD5;
+       }
+
+       /* remove truncation, first byte is key length */
+       if (*P.ptr >= P.len)
+       {       /* decryption failed? */
+               return chunk_empty;
+       }
+       return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
+}
+
+METHOD(radius_socket_t, decrypt_msk, chunk_t,
+       private_radius_socket_t *this, radius_message_t *request,
+       radius_message_t *response)
+{
+       struct {
+               u_int32_t id;
+               u_int8_t type;
+               u_int8_t length;
+               u_int16_t salt;
+               u_int8_t key[];
+       } __attribute__((packed)) *mppe_key;
+       enumerator_t *enumerator;
+       chunk_t data, send = chunk_empty, recv = chunk_empty;
+       int type;
+
+       enumerator = response->create_enumerator(response);
+       while (enumerator->enumerate(enumerator, &type, &data))
+       {
+               if (type == RAT_VENDOR_SPECIFIC &&
+                       data.len > sizeof(*mppe_key))
+               {
+                       mppe_key = (void*)data.ptr;
+                       if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT &&
+                               mppe_key->length == data.len - sizeof(mppe_key->id))
+                       {
+                               data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key));
+                               if (mppe_key->type == MS_MPPE_SEND_KEY)
+                               {
+                                       send = decrypt_mppe_key(this, mppe_key->salt, data, request);
+                               }
+                               if (mppe_key->type == MS_MPPE_RECV_KEY)
+                               {
+                                       recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
+                               }
+                       }
+               }
+       }
+       enumerator->destroy(enumerator);
+       if (send.ptr && recv.ptr)
+       {
+               return chunk_cat("mm", recv, send);
+       }
+       chunk_clear(&send);
+       chunk_clear(&recv);
+       return chunk_empty;
+}
+
+METHOD(radius_socket_t, destroy, void,
+       private_radius_socket_t *this)
+{
+       DESTROY_IF(this->hasher);
+       DESTROY_IF(this->signer);
+       DESTROY_IF(this->rng);
+       close(this->fd);
+       free(this);
+}
+
+/**
+ * See header
+ */
+radius_socket_t *radius_socket_create(host_t *host, chunk_t secret)
+{
+       private_radius_socket_t *this;
+
+       INIT(this,
+               .public = {
+                       .request = _request,
+                       .decrypt_msk = _decrypt_msk,
+                       .destroy = _destroy,
+               },
+       );
+
+       this->fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP);
+       if (this->fd < 0)
+       {
+               DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno));
+               free(this);
+               return NULL;
+       }
+       if (connect(this->fd, host->get_sockaddr(host),
+                               *host->get_sockaddr_len(host)) < 0)
+       {
+               DBG1(DBG_CFG, "connecting RADIUS socket failed");
+               close(this->fd);
+               free(this);
+               return NULL;
+       }
+       this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
+       this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);
+       this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
+       if (!this->hasher || !this->signer || !this->rng)
+       {
+               DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
+               destroy(this);
+               return NULL;
+       }
+       this->secret = secret;
+       this->signer->set_key(this->signer, secret);
+       /* we use a random identifier, helps if we restart often */
+       this->identifier = random();
+
+       return &this->public;
+}
diff --git a/src/libcharon/plugins/eap_radius/radius_socket.h b/src/libcharon/plugins/eap_radius/radius_socket.h
new file mode 100644 (file)
index 0000000..fe8491a
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 radius_socket radius_socket
+ * @{ @ingroup eap_radius
+ */
+
+#ifndef RADIUS_SOCKET_H_
+#define RADIUS_SOCKET_H_
+
+typedef struct radius_socket_t radius_socket_t;
+
+#include "radius_message.h"
+
+#include <utils/host.h>
+
+/**
+ * RADIUS socket to a server.
+ */
+struct radius_socket_t {
+
+       /**
+        * Send a RADIUS request, wait for response.
+
+        * The socket fills in RADIUS Message identifier, builds a
+        * Request-Authenticator and calculates the Message-Authenticator
+        * attribute.
+        * The received response gets verified using the Response-Identifier
+        * and the Message-Authenticator attribute.
+        *
+        * @param request               request message
+        * @return                              response message, NULL if timed out
+        */
+       radius_message_t* (*request)(radius_socket_t *this,
+                                                                radius_message_t *request);
+
+       /**
+        * Decrypt the MSK encoded in a messages MS-MPPE-Send/Recv-Key.
+        *
+        * @param request               associated RADIUS request message
+        * @param response              RADIUS response message containing attributes
+        * @return                              allocated MSK, empty chunk if none found
+        */
+       chunk_t (*decrypt_msk)(radius_socket_t *this, radius_message_t *request,
+                                                  radius_message_t *response);
+
+       /**
+        * Destroy a radius_socket_t.
+        */
+       void (*destroy)(radius_socket_t *this);
+};
+
+/**
+ * Create a radius_socket instance.
+ *
+ * @param host         RADIUS server address to connect to
+ * @param secret       RADIUS secret
+ */
+radius_socket_t *radius_socket_create(host_t *host, chunk_t secret);
+
+#endif /** RADIUS_SOCKET_H_ @}*/