From b8064f4099997a9e2179f3ad4ace605f5ccac3a1 Mon Sep 17 00:00:00 2001 From: Rene Mayrhofer Date: Mon, 9 Aug 2010 08:09:54 +0000 Subject: [svn-upgrade] new version strongswan (4.4.1) --- src/libcharon/plugins/eap_radius/Makefile.am | 2 + src/libcharon/plugins/eap_radius/Makefile.in | 9 +- src/libcharon/plugins/eap_radius/eap_radius.c | 131 +++--- .../plugins/eap_radius/eap_radius_plugin.c | 150 ++++++- .../plugins/eap_radius/eap_radius_plugin.h | 8 + src/libcharon/plugins/eap_radius/radius_client.c | 464 ++++----------------- src/libcharon/plugins/eap_radius/radius_client.h | 34 +- src/libcharon/plugins/eap_radius/radius_message.c | 123 +++--- src/libcharon/plugins/eap_radius/radius_server.c | 212 ++++++++++ src/libcharon/plugins/eap_radius/radius_server.h | 88 ++++ src/libcharon/plugins/eap_radius/radius_socket.c | 309 ++++++++++++++ src/libcharon/plugins/eap_radius/radius_socket.h | 74 ++++ 12 files changed, 1054 insertions(+), 550 deletions(-) create mode 100644 src/libcharon/plugins/eap_radius/radius_server.c create mode 100644 src/libcharon/plugins/eap_radius/radius_server.h create mode 100644 src/libcharon/plugins/eap_radius/radius_socket.c create mode 100644 src/libcharon/plugins/eap_radius/radius_socket.h (limited to 'src/libcharon/plugins/eap_radius') 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 @@ -52,11 +52,6 @@ struct private_eap_radius_t { */ u_int32_t vendor; - /** - * EAP MSK, if method established one - */ - chunk_t msk; - /** * RADIUS client instance */ @@ -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 /** - * 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, §ion)) + { + 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 +#include 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 #include @@ -24,42 +27,8 @@ #include #include -/** - * 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. */ @@ -70,171 +39,21 @@ 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 . + * + * 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 +#include +#include + +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 . + * + * 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 . + * + * 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 +#include + +#include + +/** + * 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 . + * + * 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 + +/** + * 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_ @}*/ -- cgit v1.2.3