summaryrefslogtreecommitdiff
path: root/src/libcharon/plugins/eap_radius
diff options
context:
space:
mode:
authorRene Mayrhofer <rene@mayrhofer.eu.org>2010-08-09 08:09:54 +0000
committerRene Mayrhofer <rene@mayrhofer.eu.org>2010-08-09 08:09:54 +0000
commitb8064f4099997a9e2179f3ad4ace605f5ccac3a1 (patch)
tree81778e976b476374c48b4fe83d084b986b890421 /src/libcharon/plugins/eap_radius
parent1ac70afcc1f7d6d2738a34308810719b0976d29f (diff)
downloadvyos-strongswan-b8064f4099997a9e2179f3ad4ace605f5ccac3a1.tar.gz
vyos-strongswan-b8064f4099997a9e2179f3ad4ace605f5ccac3a1.zip
[svn-upgrade] new version strongswan (4.4.1)
Diffstat (limited to 'src/libcharon/plugins/eap_radius')
-rw-r--r--src/libcharon/plugins/eap_radius/Makefile.am2
-rw-r--r--src/libcharon/plugins/eap_radius/Makefile.in9
-rw-r--r--src/libcharon/plugins/eap_radius/eap_radius.c131
-rw-r--r--src/libcharon/plugins/eap_radius/eap_radius_plugin.c150
-rw-r--r--src/libcharon/plugins/eap_radius/eap_radius_plugin.h8
-rw-r--r--src/libcharon/plugins/eap_radius/radius_client.c464
-rw-r--r--src/libcharon/plugins/eap_radius/radius_client.h34
-rw-r--r--src/libcharon/plugins/eap_radius/radius_message.c123
-rw-r--r--src/libcharon/plugins/eap_radius/radius_server.c212
-rw-r--r--src/libcharon/plugins/eap_radius/radius_server.h88
-rw-r--r--src/libcharon/plugins/eap_radius/radius_socket.c309
-rw-r--r--src/libcharon/plugins/eap_radius/radius_socket.h74
12 files changed, 1054 insertions, 550 deletions
diff --git a/src/libcharon/plugins/eap_radius/Makefile.am b/src/libcharon/plugins/eap_radius/Makefile.am
index a3abd4124..afc50bced 100644
--- a/src/libcharon/plugins/eap_radius/Makefile.am
+++ b/src/libcharon/plugins/eap_radius/Makefile.am
@@ -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
diff --git a/src/libcharon/plugins/eap_radius/Makefile.in b/src/libcharon/plugins/eap_radius/Makefile.in
index 18427adef..bb372d13c 100644
--- a/src/libcharon/plugins/eap_radius/Makefile.in
+++ b/src/libcharon/plugins/eap_radius/Makefile.in
@@ -1,4 +1,4 @@
-# Makefile.in generated by automake 1.11 from Makefile.am.
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
@@ -75,7 +75,8 @@ am__installdirs = "$(DESTDIR)$(plugindir)"
LTLIBRARIES = $(noinst_LTLIBRARIES) $(plugin_LTLIBRARIES)
libstrongswan_eap_radius_la_LIBADD =
am_libstrongswan_eap_radius_la_OBJECTS = eap_radius_plugin.lo \
- eap_radius.lo radius_client.lo radius_message.lo
+ eap_radius.lo radius_server.lo radius_socket.lo \
+ radius_client.lo radius_message.lo
libstrongswan_eap_radius_la_OBJECTS = \
$(am_libstrongswan_eap_radius_la_OBJECTS)
libstrongswan_eap_radius_la_LINK = $(LIBTOOL) --tag=CC \
@@ -267,6 +268,8 @@ AM_CFLAGS = -rdynamic
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
@@ -358,6 +361,8 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eap_radius_plugin.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radius_client.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radius_message.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radius_server.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radius_socket.Plo@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
diff --git a/src/libcharon/plugins/eap_radius/eap_radius.c b/src/libcharon/plugins/eap_radius/eap_radius.c
index f041fda54..65b868bc6 100644
--- a/src/libcharon/plugins/eap_radius/eap_radius.c
+++ b/src/libcharon/plugins/eap_radius/eap_radius.c
@@ -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;
@@ -71,6 +66,11 @@ struct private_eap_radius_t {
* Prefix to prepend to EAP identity
*/
char *id_prefix;
+
+ /**
+ * Handle the Class attribute as group membership information?
+ */
+ bool class_group;
};
/**
@@ -140,10 +140,8 @@ static bool radius2ike(private_eap_radius_t *this,
return FALSE;
}
-/**
- * Implementation of eap_method_t.initiate
- */
-static status_t initiate(private_eap_radius_t *this, eap_payload_t **out)
+METHOD(eap_method_t, initiate, status_t,
+ private_eap_radius_t *this, eap_payload_t **out)
{
radius_message_t *request, *response;
status_t status = FAILED;
@@ -177,10 +175,44 @@ static status_t initiate(private_eap_radius_t *this, eap_payload_t **out)
}
/**
- * Implementation of eap_method_t.process
+ * Handle the Class attribute as group membership information
*/
-static status_t process(private_eap_radius_t *this,
- eap_payload_t *in, eap_payload_t **out)
+static void process_class(private_eap_radius_t *this, radius_message_t *msg)
+{
+ enumerator_t *enumerator;
+ chunk_t data;
+ int type;
+
+ enumerator = msg->create_enumerator(msg);
+ while (enumerator->enumerate(enumerator, &type, &data))
+ {
+ if (type == RAT_CLASS)
+ {
+ identification_t *id;
+ ike_sa_t *ike_sa;
+ auth_cfg_t *auth;
+
+ if (data.len >= 44)
+ { /* quirk: ignore long class attributes, these are used for
+ * other purposes by some RADIUS servers (such as NPS). */
+ continue;
+ }
+
+ ike_sa = charon->bus->get_sa(charon->bus);
+ if (ike_sa)
+ {
+ auth = ike_sa->get_auth_cfg(ike_sa, FALSE);
+ id = identification_create_from_data(data);
+ DBG1(DBG_CFG, "received group membership '%Y' from RADIUS", id);
+ auth->add(auth, AUTH_RULE_GROUP, id);
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+}
+
+METHOD(eap_method_t, process, status_t,
+ private_eap_radius_t *this, eap_payload_t *in, eap_payload_t **out)
{
radius_message_t *request, *response;
status_t status = FAILED;
@@ -211,8 +243,10 @@ static status_t process(private_eap_radius_t *this,
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);
+ }
status = SUCCESS;
break;
case RMC_ACCESS_REJECT:
@@ -228,32 +262,29 @@ static status_t process(private_eap_radius_t *this,
return status;
}
-/**
- * Implementation of eap_method_t.get_type.
- */
-static eap_type_t get_type(private_eap_radius_t *this, u_int32_t *vendor)
+METHOD(eap_method_t, get_type, eap_type_t,
+ private_eap_radius_t *this, u_int32_t *vendor)
{
*vendor = this->vendor;
return this->type;
}
-/**
- * Implementation of eap_method_t.get_msk.
- */
-static status_t get_msk(private_eap_radius_t *this, chunk_t *msk)
+METHOD(eap_method_t, get_msk, status_t,
+ 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;
}
-/**
- * Implementation of eap_method_t.is_mutual.
- */
-static bool is_mutual(private_eap_radius_t *this)
+METHOD(eap_method_t, is_mutual, bool,
+ private_eap_radius_t *this)
{
switch (this->type)
{
@@ -265,15 +296,12 @@ static bool is_mutual(private_eap_radius_t *this)
}
}
-/**
- * Implementation of eap_method_t.destroy.
- */
-static void destroy(private_eap_radius_t *this)
+METHOD(eap_method_t, destroy, void,
+ private_eap_radius_t *this)
{
this->peer->destroy(this->peer);
this->server->destroy(this->server);
this->client->destroy(this->client);
- chunk_clear(&this->msk);
free(this);
}
@@ -282,15 +310,26 @@ static void destroy(private_eap_radius_t *this)
*/
eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer)
{
- private_eap_radius_t *this = malloc_thing(private_eap_radius_t);
-
- this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate;
- this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process;
- this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*,u_int32_t*))get_type;
- this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual;
- this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk;
- this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy;
-
+ private_eap_radius_t *this;
+
+ INIT(this,
+ .public.eap_method_interface = {
+ .initiate = _initiate,
+ .process = _process,
+ .get_type = _get_type,
+ .is_mutual = _is_mutual,
+ .get_msk = _get_msk,
+ .destroy = _destroy,
+ },
+ /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
+ .type = EAP_RADIUS,
+ .eap_start = lib->settings->get_bool(lib->settings,
+ "charon.plugins.eap-radius.eap_start", FALSE),
+ .id_prefix = lib->settings->get_str(lib->settings,
+ "charon.plugins.eap-radius.id_prefix", ""),
+ .class_group = lib->settings->get_bool(lib->settings,
+ "charon.plugins.eap-radius.class_group", FALSE),
+ );
this->client = radius_client_create();
if (!this->client)
{
@@ -299,14 +338,6 @@ eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer
}
this->peer = peer->clone(peer);
this->server = server->clone(server);
- /* initially EAP_RADIUS, but is set to the method selected by RADIUS */
- this->type = EAP_RADIUS;
- this->vendor = 0;
- this->msk = chunk_empty;
- this->eap_start = lib->settings->get_bool(lib->settings,
- "charon.plugins.eap-radius.eap_start", FALSE);
- this->id_prefix = lib->settings->get_str(lib->settings,
- "charon.plugins.eap-radius.id_prefix", "");
return &this->public;
}
diff --git a/src/libcharon/plugins/eap_radius/eap_radius_plugin.c b/src/libcharon/plugins/eap_radius/eap_radius_plugin.c
index 7d2788c3e..91aae2f62 100644
--- a/src/libcharon/plugins/eap_radius/eap_radius_plugin.c
+++ b/src/libcharon/plugins/eap_radius/eap_radius_plugin.c
@@ -17,17 +17,130 @@
#include "eap_radius.h"
#include "radius_client.h"
+#include "radius_server.h"
#include <daemon.h>
/**
- * Implementation of plugin_t.destroy
+ * Default RADIUS server port, when not configured
*/
-static void destroy(eap_radius_plugin_t *this)
+#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,
+ 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;
+}
+
+/**
+ * Load RADIUS servers from configuration
+ */
+static bool load_servers(private_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;
+ }
+
+ enumerator = lib->settings->create_section_enumerator(lib->settings,
+ "charon.plugins.eap-radius.servers");
+ while (enumerator->enumerate(enumerator, &section))
+ {
+ 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;
}
/*
@@ -35,20 +148,35 @@ static void destroy(eap_radius_plugin_t *this)
*/
plugin_t *eap_radius_plugin_create()
{
- eap_radius_plugin_t *this;
+ private_eap_radius_plugin_t *this;
+
+ INIT(this,
+ .public.plugin.destroy = _destroy,
+ .servers = linked_list_create(),
+ );
- if (!radius_client_init())
+ if (!load_servers(this))
{
- DBG1(DBG_CFG, "RADIUS plugin initialization failed");
+ destroy(this);
return NULL;
}
-
- this = malloc_thing(eap_radius_plugin_t);
- this->plugin.destroy = (void(*)(plugin_t*))destroy;
-
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();
}
diff --git a/src/libcharon/plugins/eap_radius/eap_radius_plugin.h b/src/libcharon/plugins/eap_radius/eap_radius_plugin.h
index f2b8b5082..cb724364a 100644
--- a/src/libcharon/plugins/eap_radius/eap_radius_plugin.h
+++ b/src/libcharon/plugins/eap_radius/eap_radius_plugin.h
@@ -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_ @}*/
diff --git a/src/libcharon/plugins/eap_radius/radius_client.c b/src/libcharon/plugins/eap_radius/radius_client.c
index 1d1f21742..232b9135e 100644
--- a/src/libcharon/plugins/eap_radius/radius_client.c
+++ b/src/libcharon/plugins/eap_radius/radius_client.c
@@ -15,6 +15,9 @@
#include "radius_client.h"
+#include "eap_radius_plugin.h"
+#include "radius_server.h"
+
#include <unistd.h>
#include <errno.h>
@@ -24,42 +27,8 @@
#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
@@ -261,234 +80,103 @@ static void save_state(private_radius_client_t *this, radius_message_t *msg)
chunk_free(&this->state);
}
-/**
- * Implementation of radius_client_t.request
- */
-static radius_message_t* request(private_radius_client_t *this,
- radius_message_t *req)
+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;
+ radius_socket_t *socket;
+ radius_message_t *res;
- socket = get_socket();
-
- /* 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)
+ 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)
{
- 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)
- {
- 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)
+METHOD(radius_client_t, get_msk, chunk_t,
+ private_radius_client_t *this)
{
- 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);
+ return this->msk;
+}
- /* 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, destroy, void,
+ private_radius_client_t *this)
+{
+ chunk_clear(&this->msk);
+ free(this->state.ptr);
+ free(this);
}
/**
- * Implementation of radius_client_t.decrypt_msk
+ * See header
*/
-static chunk_t decrypt_msk(private_radius_client_t *this,
- radius_message_t *response, radius_message_t *request)
+radius_client_t *radius_client_create()
{
- 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;
+ private_radius_client_t *this;
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))
+ radius_server_t *server;
+ int current, best = -1;
+
+ INIT(this,
+ .public = {
+ .request = _request,
+ .get_msk = _get_msk,
+ .destroy = _destroy,
+ },
+ );
+
+ enumerator = eap_radius_create_server_enumerator();
+ while (enumerator->enumerate(enumerator, &server))
{
- if (type == RAT_VENDOR_SPECIFIC &&
- data.len > sizeof(*mppe_key))
+ 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
{
- 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);
- }
- }
+ DBG2(DBG_CFG, "RADIUS server %H skipped: %d",
+ server->get_address(server), current);
}
}
enumerator->destroy(enumerator);
- if (send.ptr && recv.ptr)
+
+ if (!this->server)
{
- return chunk_cat("mm", recv, send);
+ free(this);
+ return NULL;
}
- chunk_clear(&send);
- chunk_clear(&recv);
- return chunk_empty;
-}
-
-/**
- * Implementation of radius_client_t.destroy.
- */
-static void destroy(private_radius_client_t *this)
-{
- free(this->state.ptr);
- free(this);
-}
-
-/**
- * See header
- */
-radius_client_t *radius_client_create()
-{
- private_radius_client_t *this = malloc_thing(private_radius_client_t);
-
- this->public.request = (radius_message_t*(*)(radius_client_t*, radius_message_t *msg))request;
- this->public.decrypt_msk = (chunk_t(*)(radius_client_t*, radius_message_t *, radius_message_t *))decrypt_msk;
- this->public.destroy = (void(*)(radius_client_t*))destroy;
-
- this->state = chunk_empty;
return &this->public;
}
diff --git a/src/libcharon/plugins/eap_radius/radius_client.h b/src/libcharon/plugins/eap_radius/radius_client.h
index 77ba94807..e4f3a7222 100644
--- a/src/libcharon/plugins/eap_radius/radius_client.h
+++ b/src/libcharon/plugins/eap_radius/radius_client.h
@@ -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_message.c b/src/libcharon/plugins/eap_radius/radius_message.c
index 11a1d8dfc..23a29b772 100644
--- a/src/libcharon/plugins/eap_radius/radius_message.c
+++ b/src/libcharon/plugins/eap_radius/radius_message.c
@@ -215,13 +215,8 @@ typedef struct {
int left;
} attribute_enumerator_t;
-
-/**
- * Implementation of attribute_enumerator_t.enumerate
- */
-static bool attribute_enumerate(attribute_enumerator_t *this,
- int *type, chunk_t *data)
-
+METHOD(enumerator_t, attribute_enumerate, bool,
+ attribute_enumerator_t *this, int *type, chunk_t *data)
{
if (this->left == 0)
{
@@ -241,10 +236,8 @@ static bool attribute_enumerate(attribute_enumerator_t *this,
return TRUE;
}
-/**
- * Implementation of radius_message_t.create_enumerator
- */
-static enumerator_t* create_enumerator(private_radius_message_t *this)
+METHOD(radius_message_t, create_enumerator, enumerator_t*,
+ private_radius_message_t *this)
{
attribute_enumerator_t *e;
@@ -252,20 +245,19 @@ static enumerator_t* create_enumerator(private_radius_message_t *this)
{
return enumerator_create_empty();
}
-
- e = malloc_thing(attribute_enumerator_t);
- e->public.enumerate = (void*)attribute_enumerate;
- e->public.destroy = (void*)free;
- e->next = (rattr_t*)this->msg->attributes;
- e->left = ntohs(this->msg->length) - sizeof(rmsg_t);
+ INIT(e,
+ .public = {
+ .enumerate = (void*)_attribute_enumerate,
+ .destroy = (void*)free,
+ },
+ .next = (rattr_t*)this->msg->attributes,
+ .left = ntohs(this->msg->length) - sizeof(rmsg_t),
+ );
return &e->public;
}
-/**
- * Implementation of radius_message_t.add
- */
-static void add(private_radius_message_t *this, radius_attribute_type_t type,
- chunk_t data)
+METHOD(radius_message_t, add, void,
+ private_radius_message_t *this, radius_attribute_type_t type, chunk_t data)
{
rattr_t *attribute;
@@ -279,10 +271,8 @@ static void add(private_radius_message_t *this, radius_attribute_type_t type,
this->msg->length = htons(ntohs(this->msg->length) + attribute->length);
}
-/**
- * Implementation of radius_message_t.sign
- */
-static void sign(private_radius_message_t *this, rng_t *rng, signer_t *signer)
+METHOD(radius_message_t, sign, void,
+ private_radius_message_t *this, rng_t *rng, signer_t *signer)
{
char buf[HASH_SIZE_MD5];
@@ -297,11 +287,9 @@ static void sign(private_radius_message_t *this, rng_t *rng, signer_t *signer)
((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5);
}
-/**
- * Implementation of radius_message_t.verify
- */
-static bool verify(private_radius_message_t *this, u_int8_t *req_auth,
- chunk_t secret, hasher_t *hasher, signer_t *signer)
+METHOD(radius_message_t, verify, bool,
+ private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
+ hasher_t *hasher, signer_t *signer)
{
char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5];
enumerator_t *enumerator;
@@ -369,51 +357,39 @@ static bool verify(private_radius_message_t *this, u_int8_t *req_auth,
return TRUE;
}
-/**
- * Implementation of radius_message_t.get_code
- */
-static radius_message_code_t get_code(private_radius_message_t *this)
+METHOD(radius_message_t, get_code, radius_message_code_t,
+ private_radius_message_t *this)
{
return this->msg->code;
}
-/**
- * Implementation of radius_message_t.get_identifier
- */
-static u_int8_t get_identifier(private_radius_message_t *this)
+METHOD(radius_message_t, get_identifier, u_int8_t,
+ private_radius_message_t *this)
{
return this->msg->identifier;
}
-/**
- * Implementation of radius_message_t.set_identifier
- */
-static void set_identifier(private_radius_message_t *this, u_int8_t identifier)
+METHOD(radius_message_t, set_identifier, void,
+ private_radius_message_t *this, u_int8_t identifier)
{
this->msg->identifier = identifier;
}
-/**
- * Implementation of radius_message_t.get_authenticator
- */
-static u_int8_t* get_authenticator(private_radius_message_t *this)
+METHOD(radius_message_t, get_authenticator, u_int8_t*,
+ private_radius_message_t *this)
{
return this->msg->authenticator;
}
-/**
- * Implementation of radius_message_t.get_encoding
- */
-static chunk_t get_encoding(private_radius_message_t *this)
+METHOD(radius_message_t, get_encoding, chunk_t,
+ private_radius_message_t *this)
{
return chunk_create((u_char*)this->msg, ntohs(this->msg->length));
}
-/**
- * Implementation of radius_message_t.destroy.
- */
-static void destroy(private_radius_message_t *this)
+METHOD(radius_message_t, destroy, void,
+ private_radius_message_t *this)
{
free(this->msg);
free(this);
@@ -424,18 +400,22 @@ static void destroy(private_radius_message_t *this)
*/
static private_radius_message_t *radius_message_create()
{
- private_radius_message_t *this = malloc_thing(private_radius_message_t);
-
- this->public.create_enumerator = (enumerator_t*(*)(radius_message_t*))create_enumerator;
- this->public.add = (void(*)(radius_message_t*, radius_attribute_type_t,chunk_t))add;
- this->public.get_code = (radius_message_code_t(*)(radius_message_t*))get_code;
- this->public.get_identifier = (u_int8_t(*)(radius_message_t*))get_identifier;
- this->public.set_identifier = (void(*)(radius_message_t*, u_int8_t identifier))set_identifier;
- this->public.get_authenticator = (u_int8_t*(*)(radius_message_t*))get_authenticator;
- this->public.get_encoding = (chunk_t(*)(radius_message_t*))get_encoding;
- this->public.sign = (void(*)(radius_message_t*, rng_t *rng, signer_t *signer))sign;
- this->public.verify = (bool(*)(radius_message_t*, u_int8_t *req_auth, chunk_t secret, hasher_t *hasher, signer_t *signer))verify;
- this->public.destroy = (void(*)(radius_message_t*))destroy;
+ private_radius_message_t *this;
+
+ INIT(this,
+ .public = {
+ .create_enumerator = _create_enumerator,
+ .add = _add,
+ .get_code = _get_code,
+ .get_identifier = _get_identifier,
+ .set_identifier = _set_identifier,
+ .get_authenticator = _get_authenticator,
+ .get_encoding = _get_encoding,
+ .sign = _sign,
+ .verify = _verify,
+ .destroy = _destroy,
+ },
+ );
return this;
}
@@ -447,10 +427,11 @@ radius_message_t *radius_message_create_request()
{
private_radius_message_t *this = radius_message_create();
- this->msg = malloc_thing(rmsg_t);
- this->msg->code = RMC_ACCESS_REQUEST;
- this->msg->identifier = 0;
- this->msg->length = htons(sizeof(rmsg_t));
+ INIT(this->msg,
+ .code = RMC_ACCESS_REQUEST,
+ .identifier = 0,
+ .length = htons(sizeof(rmsg_t)),
+ );
return &this->public;
}
diff --git a/src/libcharon/plugins/eap_radius/radius_server.c b/src/libcharon/plugins/eap_radius/radius_server.c
new file mode 100644
index 000000000..f54b8b2cd
--- /dev/null
+++ b/src/libcharon/plugins/eap_radius/radius_server.c
@@ -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
index 000000000..b820cb583
--- /dev/null
+++ b/src/libcharon/plugins/eap_radius/radius_server.h
@@ -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
index 000000000..f46c27ede
--- /dev/null
+++ b/src/libcharon/plugins/eap_radius/radius_socket.c
@@ -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
index 000000000..fe8491a8f
--- /dev/null
+++ b/src/libcharon/plugins/eap_radius/radius_socket.h
@@ -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_ @}*/