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.c495
1 files changed, 495 insertions, 0 deletions
diff --git a/src/libcharon/plugins/eap_radius/radius_client.c b/src/libcharon/plugins/eap_radius/radius_client.c
new file mode 100644
index 000000000..1d1f21742
--- /dev/null
+++ b/src/libcharon/plugins/eap_radius/radius_client.c
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2009 Martin Willi
+ * Hochschule fuer Technik Rapperswil
+ *
+ * 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_client.h"
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <daemon.h>
+#include <utils/host.h>
+#include <utils/linked_list.h>
+#include <threading/condvar.h>
+#include <threading/mutex.h>
+
+/**
+ * Default RADIUS server port, when not configured
+ */
+#define RADIUS_PORT 1812
+
+/**
+ * Vendor-Id of Microsoft specific attributes
+ */
+#define VENDOR_ID_MICROSOFT 311
+
+/**
+ * Microsoft specific vendor attributes
+ */
+#define MS_MPPE_SEND_KEY 16
+#define MS_MPPE_RECV_KEY 17
+
+typedef struct private_radius_client_t private_radius_client_t;
+
+typedef struct entry_t entry_t;
+
+/**
+ * A socket pool entry.
+ */
+struct entry_t {
+ /** socket file descriptor */
+ int fd;
+ /** current RADIUS identifier */
+ u_int8_t identifier;
+ /** hasher to use for response verification */
+ hasher_t *hasher;
+ /** HMAC-MD5 signer to build Message-Authenticator attribute */
+ signer_t *signer;
+ /** random number generator for RADIUS request authenticator */
+ rng_t *rng;
+};
+
+/**
+ * Private data of an radius_client_t object.
+ */
+struct private_radius_client_t {
+
+ /**
+ * Public radius_client_t interface.
+ */
+ radius_client_t public;
+
+ /**
+ * 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);
+}
+
+/**
+ * Save the state attribute to include in further request
+ */
+static void save_state(private_radius_client_t *this, radius_message_t *msg)
+{
+ enumerator_t *enumerator;
+ int type;
+ chunk_t data;
+
+ enumerator = msg->create_enumerator(msg);
+ while (enumerator->enumerate(enumerator, &type, &data))
+ {
+ if (type == RAT_STATE)
+ {
+ free(this->state.ptr);
+ this->state = chunk_clone(data);
+ enumerator->destroy(enumerator);
+ return;
+ }
+ }
+ enumerator->destroy(enumerator);
+ /* no state attribute found, remove state */
+ chunk_free(&this->state);
+}
+
+/**
+ * Implementation of radius_client_t.request
+ */
+static radius_message_t* request(private_radius_client_t *this,
+ radius_message_t *req)
+{
+ char virtual[] = {0x00,0x00,0x00,0x05};
+ entry_t *socket;
+ chunk_t data;
+ int i;
+
+ socket = get_socket();
+
+ /* 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);
+ /* 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++)
+ {
+ radius_message_t *response;
+ bool retransmit = FALSE;
+ struct timeval tv;
+ char buf[4096];
+ fd_set fds;
+ int res;
+
+ if (send(socket->fd, data.ptr, data.len, 0) != data.len)
+ {
+ DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
+ put_socket(socket);
+ return NULL;
+ }
+ tv.tv_sec = i;
+ tv.tv_usec = 0;
+
+ while (TRUE)
+ {
+ FD_ZERO(&fds);
+ FD_SET(socket->fd, &fds);
+ res = select(socket->fd + 1, &fds, NULL, NULL, &tv);
+ /* TODO: updated tv to time not waited. Linux does this for us. */
+ if (res < 0)
+ { /* failed */
+ DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
+ strerror(errno));
+ break;
+ }
+ if (res == 0)
+ { /* timeout */
+ DBG1(DBG_CFG, "retransmitting RADIUS message");
+ retransmit = TRUE;
+ break;
+ }
+ res = recv(socket->fd, buf, sizeof(buf), MSG_DONTWAIT);
+ if (res <= 0)
+ {
+ DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
+ strerror(errno));
+ break;
+ }
+ response = radius_message_parse_response(chunk_create(buf, res));
+ if (response)
+ {
+ if (response->verify(response, req->get_authenticator(req),
+ secret, socket->hasher, socket->signer))
+ {
+ save_state(this, response);
+ put_socket(socket);
+ return response;
+ }
+ response->destroy(response);
+ }
+ DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
+ }
+ if (!retransmit)
+ {
+ break;
+ }
+ }
+ DBG1(DBG_CFG, "RADIUS server is not responding");
+ put_socket(socket);
+ charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING);
+ return NULL;
+}
+
+/**
+ * Decrypt a MS-MPPE-Send/Recv-Key
+ */
+static chunk_t decrypt_mppe_key(private_radius_client_t *this, u_int16_t salt,
+ chunk_t C, radius_message_t *request)
+{
+ chunk_t A, R, P, seed;
+ u_char *c, *p;
+ hasher_t *hasher;
+
+ /**
+ * From RFC2548 (encryption):
+ * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
+ * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
+ * . . .
+ * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
+ */
+
+ if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
+ {
+ return chunk_empty;
+ }
+
+ hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
+ if (!hasher)
+ {
+ return chunk_empty;
+ }
+
+ A = chunk_create((u_char*)&salt, sizeof(salt));
+ R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
+ P = chunk_alloca(C.len);
+ p = P.ptr;
+ c = C.ptr;
+
+ seed = chunk_cata("cc", R, A);
+
+ while (c < C.ptr + C.len)
+ {
+ /* b(i) = MD5(S + c(i-1)) */
+ hasher->get_hash(hasher, secret, NULL);
+ hasher->get_hash(hasher, seed, p);
+
+ /* p(i) = b(i) xor c(1) */
+ memxor(p, c, HASH_SIZE_MD5);
+
+ /* prepare next round */
+ seed = chunk_create(c, HASH_SIZE_MD5);
+ c += HASH_SIZE_MD5;
+ p += HASH_SIZE_MD5;
+ }
+ hasher->destroy(hasher);
+
+ /* remove truncation, first byte is key length */
+ if (*P.ptr >= P.len)
+ { /* decryption failed? */
+ return chunk_empty;
+ }
+ return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
+}
+
+/**
+ * Implementation of radius_client_t.decrypt_msk
+ */
+static chunk_t decrypt_msk(private_radius_client_t *this,
+ radius_message_t *response, radius_message_t *request)
+{
+ 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;
+}
+
+/**
+ * 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;
+}
+