summaryrefslogtreecommitdiff
path: root/src/libcharon/plugins/eap_radius/radius_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/plugins/eap_radius/radius_client.c')
-rw-r--r--src/libcharon/plugins/eap_radius/radius_client.c464
1 files changed, 76 insertions, 388 deletions
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;
}