summaryrefslogtreecommitdiff
path: root/src/libradius/radius_socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libradius/radius_socket.c')
-rw-r--r--src/libradius/radius_socket.c373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/libradius/radius_socket.c b/src/libradius/radius_socket.c
new file mode 100644
index 000000000..048c8814e
--- /dev/null
+++ b/src/libradius/radius_socket.c
@@ -0,0 +1,373 @@
+/*
+ * 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 "radius_mppe.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <pen/pen.h>
+#include <debug.h>
+
+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;
+
+ /**
+ * Server port for authentication
+ */
+ u_int16_t auth_port;
+
+ /**
+ * socket file descriptor for authentication
+ */
+ int auth_fd;
+
+ /**
+ * Server port for accounting
+ */
+ u_int16_t acct_port;
+
+ /**
+ * socket file descriptor for accounting
+ */
+ int acct_fd;
+
+ /**
+ * Server address
+ */
+ char *address;
+
+ /**
+ * 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;
+};
+
+/**
+ * Check or establish RADIUS connection
+ */
+static bool check_connection(private_radius_socket_t *this,
+ int *fd, u_int16_t port)
+{
+ if (*fd == -1)
+ {
+ host_t *server;
+
+ server = host_create_from_dns(this->address, AF_UNSPEC, port);
+ if (!server)
+ {
+ DBG1(DBG_CFG, "resolving RADIUS server address '%s' failed",
+ this->address);
+ return FALSE;
+ }
+ *fd = socket(server->get_family(server), SOCK_DGRAM, IPPROTO_UDP);
+ if (*fd == -1)
+ {
+ DBG1(DBG_CFG, "opening RADIUS socket for %#H failed: %s",
+ server, strerror(errno));
+ server->destroy(server);
+ return FALSE;
+ }
+ if (connect(*fd, server->get_sockaddr(server),
+ *server->get_sockaddr_len(server)) < 0)
+ {
+ DBG1(DBG_CFG, "connecting RADIUS socket to %#H failed: %s",
+ server, strerror(errno));
+ server->destroy(server);
+ close(*fd);
+ *fd = -1;
+ return FALSE;
+ }
+ server->destroy(server);
+ }
+ return TRUE;
+}
+
+METHOD(radius_socket_t, request, radius_message_t*,
+ private_radius_socket_t *this, radius_message_t *request)
+{
+ chunk_t data;
+ int i, *fd;
+ u_int16_t port;
+ rng_t *rng = NULL;
+
+ if (request->get_code(request) == RMC_ACCOUNTING_REQUEST)
+ {
+ fd = &this->acct_fd;
+ port = this->acct_port;
+ }
+ else
+ {
+ fd = &this->auth_fd;
+ port = this->auth_port;
+ rng = this->rng;
+ }
+
+ /* set Message Identifier */
+ request->set_identifier(request, this->identifier++);
+ /* sign the request */
+ request->sign(request, NULL, this->secret, this->hasher, this->signer,
+ rng, rng != NULL);
+
+ if (!check_connection(this, fd, port))
+ {
+ return NULL;
+ }
+
+ data = request->get_encoding(request);
+ DBG3(DBG_CFG, "%B", &data);
+
+ /* 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(*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(*fd, &fds);
+ res = select((*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(*fd, buf, sizeof(buf), MSG_DONTWAIT);
+ if (res <= 0)
+ {
+ DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
+ strerror(errno));
+ break;
+ }
+ response = radius_message_parse(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)
+{
+ mppe_key_t *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_t))
+ {
+ mppe_key = (mppe_key_t*)data.ptr;
+ if (ntohl(mppe_key->id) == PEN_MICROSOFT &&
+ mppe_key->length == data.len - sizeof(mppe_key->id))
+ {
+ data = chunk_create(mppe_key->key, data.len - sizeof(mppe_key_t));
+ 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);
+ if (this->auth_fd != -1)
+ {
+ close(this->auth_fd);
+ };
+ if (this->acct_fd != -1)
+ {
+ close(this->acct_fd);
+ }
+ free(this);
+}
+
+/**
+ * See header
+ */
+radius_socket_t *radius_socket_create(char *address, u_int16_t auth_port,
+ u_int16_t acct_port, chunk_t secret)
+{
+ private_radius_socket_t *this;
+
+ INIT(this,
+ .public = {
+ .request = _request,
+ .decrypt_msk = _decrypt_msk,
+ .destroy = _destroy,
+ },
+ .address = address,
+ .auth_port = auth_port,
+ .auth_fd = -1,
+ .acct_port = acct_port,
+ .acct_fd = -1,
+ .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
+ .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
+ .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;
+}