/*
- * Copyright (C) 2010 Andreas Steffen
+ * Copyright (C) 2012 Andreas Steffen
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
#include <debug.h>
#include <threading/thread.h>
#include <processing/jobs/callback_job.h>
+#include <sa/authenticators/eap/eap_method.h>
typedef struct private_tnc_pdp_t private_tnc_pdp_t;
*/
callback_job_t *job;
+ /**
+ * RADIUS shared secret
+ */
+ chunk_t secret;
+
+ /**
+ * MD5 hasher
+ */
+ hasher_t *hasher;
+
+ /**
+ * HMAC MD5 signer, with secret set
+ */
+ signer_t *signer;
+
+ /**
+ * EAP method
+ */
+ eap_method_t *method;
};
skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
if (skt < 0)
{
- DBG1(DBG_NET, "opening RADIUS socket failed: %s", strerror(errno));
+ DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno));
return 0;
}
if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
{
- DBG1(DBG_NET, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
+ DBG1(DBG_CFG, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
close(skt);
return 0;
}
/* bind the socket */
if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
{
- DBG1(DBG_NET, "unable to bind RADIUS socket: %s", strerror(errno));
+ DBG1(DBG_CFG, "unable to bind RADIUS socket: %s", strerror(errno));
close(skt);
return 0;
}
}
/**
+ * Send a RADIUS message to client
+ */
+static void send_message(private_tnc_pdp_t *this, radius_message_t *message,
+ host_t *client)
+{
+ int fd;
+ chunk_t data;
+
+ fd = (client->get_family(client) == AF_INET) ? this->ipv4 : this->ipv6;
+ data = message->get_encoding(message);
+
+ DBG2(DBG_CFG, "sending RADIUS packet to %#H", client);
+ DBG3(DBG_CFG, "%B", &data);
+
+ if (sendto(fd, data.ptr, data.len, 0, client->get_sockaddr(client),
+ *client->get_sockaddr_len(client)) != data.len)
+ {
+ DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
+ }
+}
+
+/**
+ * Send a RADIUS response for a request
+ */
+static void send_response(private_tnc_pdp_t *this,
+ radius_message_t *request, radius_message_code_t code,
+ eap_payload_t *eap, host_t *client)
+{
+ radius_message_t *response;
+ chunk_t data;
+
+ response = radius_message_create(code);
+ if (eap)
+ {
+ data = eap->get_data(eap);
+ response->add(response, RAT_EAP_MESSAGE, data);
+ }
+ response->set_identifier(response, request->get_identifier(request));
+ response->sign(response, request->get_authenticator(request),
+ this->secret, this->hasher, this->signer, NULL);
+
+ DBG1(DBG_CFG, "sending RADIUS %N to client '%H'", radius_message_code_names,
+ code, client);
+ send_message(this, response, client);
+}
+
+/**
+ * Process EAP message
+ */
+static void process_eap(private_tnc_pdp_t *this, radius_message_t *request,
+ host_t *source)
+{
+ enumerator_t *enumerator;
+ eap_payload_t *in, *out = NULL;
+ eap_type_t eap_type;
+ chunk_t data, message = chunk_empty;
+ radius_message_code_t code = RMC_ACCESS_CHALLENGE;
+ u_int32_t eap_vendor;
+ int type;
+
+ enumerator = request->create_enumerator(request);
+ while (enumerator->enumerate(enumerator, &type, &data))
+ {
+ if (type == RAT_EAP_MESSAGE && data.len)
+ {
+ message = chunk_cat("mc", message, data);
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ if (message.len)
+ {
+ in = eap_payload_create_data(message);
+
+ /* apply EAP method selected by RADIUS server */
+ eap_type = in->get_type(in, &eap_vendor);
+
+ DBG3(DBG_CFG, "%N payload %B", eap_type_names, eap_type, &message);
+ free(message.ptr);
+
+ if (eap_type == EAP_IDENTITY)
+ {
+ identification_t *server, *peer;
+
+ peer = identification_create_from_string("carol@strongswan.org");
+ server = identification_create_from_string("server");
+ this->method = charon->eap->create_instance(charon->eap, EAP_MD5, 0,
+ EAP_SERVER, server, peer);
+ if (!this->method)
+ {
+ peer->destroy(peer);
+ server->destroy(server);
+ in->destroy(in);
+ return;
+ }
+ this->method->initiate(this->method, &out);
+ }
+ else
+ {
+ switch (this->method->process(this->method, in, &out))
+ {
+ case NEED_MORE:
+ code = RMC_ACCESS_CHALLENGE;
+ break;
+ case SUCCESS:
+ code = RMC_ACCESS_ACCEPT;
+ break;
+ case FAILED:
+ default:
+ code = RMC_ACCESS_REJECT;
+ }
+ }
+
+ send_response(this, request, code, out, source);
+
+ in->destroy(in);
+ DESTROY_IF(out);
+ }
+}
+
+/**
* Process packets received on the RADIUS socket
*/
static job_requeue_t receive(private_tnc_pdp_t *this)
}
max_fd = max(this->ipv4, this->ipv6);
- DBG2(DBG_NET, "waiting for data on RADIUS sockets");
+ DBG2(DBG_CFG, "waiting for data on RADIUS sockets");
oldstate = thread_cancelability(TRUE);
if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
{
bytes_read = recvmsg(selected, &msg, 0);
if (bytes_read < 0)
{
- DBG1(DBG_NET, "error reading RADIUS socket: %s", strerror(errno));
+ DBG1(DBG_CFG, "error reading RADIUS socket: %s", strerror(errno));
continue;
}
if (msg.msg_flags & MSG_TRUNC)
{
- DBG1(DBG_NET, "receive buffer too small, RADIUS packet discarded");
+ DBG1(DBG_CFG, "receive buffer too small, RADIUS packet discarded");
continue;
}
source = host_create_from_sockaddr((sockaddr_t*)&src);
- DBG2(DBG_NET, "received RADIUS packet from %#H", source);
- DBG3(DBG_NET, "%b", buffer, bytes_read);
- request = radius_message_parse_response(chunk_create(buffer, bytes_read));
+ DBG2(DBG_CFG, "received RADIUS packet from %#H", source);
+ DBG3(DBG_CFG, "%b", buffer, bytes_read);
+ request = radius_message_parse(chunk_create(buffer, bytes_read));
if (request)
{
- DBG1(DBG_NET, "received RADIUS %N from client '%H'",
- radius_message_code_names, request->get_code(request), source);
+ DBG1(DBG_CFG, "received RADIUS %N from client '%H'",
+ radius_message_code_names, request->get_code(request), source);
+
+ if (request->verify(request, NULL, this->secret, this->hasher,
+ this->signer))
+ {
+ process_eap(this, request, source);
+ }
+ request->destroy(request);
+
}
else
{
- DBG1(DBG_NET, "received invalid RADIUS message, ignored");
+ DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
}
source->destroy(source);
}
METHOD(tnc_pdp_t, destroy, void,
private_tnc_pdp_t *this)
{
- this->job->cancel(this->job);
+ if (this->job)
+ {
+ this->job->cancel(this->job);
+ }
if (this->ipv4)
{
close(this->ipv4);
{
close(this->ipv6);
}
+ DESTROY_IF(this->signer);
+ DESTROY_IF(this->hasher);
+ DESTROY_IF(this->method);
free(this);
}
tnc_pdp_t *tnc_pdp_create(u_int16_t port)
{
private_tnc_pdp_t *this;
+ char *secret;
INIT(this,
.public = {
},
.ipv4 = open_socket(this, AF_INET, port),
.ipv6 = open_socket(this, AF_INET6, port),
+ .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
+ .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
);
if (!this->ipv4 && !this->ipv6)
{
DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
}
+ if (!this->hasher || !this->signer)
+ {
+ destroy(this);
+ return NULL;
+ }
+ secret = lib->settings->get_str(lib->settings,
+ "charon.plugins.tnc-pdp.secret", NULL);
+ if (!secret)
+ {
+ DBG1(DBG_CFG, "missing RADIUS secret, PDP disabled");
+ destroy(this);
+ return NULL;
+ }
+ this->secret = chunk_create(secret, strlen(secret));
+ this->signer->set_key(this->signer, this->secret);
this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
this, NULL, NULL, JOB_PRIO_CRITICAL);
private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
hasher_t *hasher, signer_t *signer, rng_t *rng)
{
- if (rng == NULL)
+ if (rng)
{
- chunk_t msg;
-
+ /* build Request-Authenticator */
+ rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator);
+ }
+ else
+ {
+ /* build Response-Authenticator */
if (req_auth)
{
memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
{
memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
}
- msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
- hasher->get_hash(hasher, msg, NULL);
- hasher->get_hash(hasher, secret, this->msg->authenticator);
}
- else
+
+ if (rng || this->msg->code == RMC_ACCESS_CHALLENGE
+ || this->msg->code == RMC_ACCESS_ACCEPT
+ || this->msg->code == RMC_ACCESS_REJECT)
{
char buf[HASH_SIZE_MD5];
- /* build Request-Authenticator */
- rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator);
-
/* build Message-Authenticator attribute, using 16 null bytes */
memset(buf, 0, sizeof(buf));
add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5);
}
+
+ if (!rng)
+ {
+ chunk_t msg;
+
+ msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
+ hasher->get_hash(hasher, msg, NULL);
+ hasher->get_hash(hasher, secret, this->msg->authenticator);
+ }
}
METHOD(radius_message_t, verify, bool,
chunk_t data, msg;
bool has_eap = FALSE, has_auth = FALSE;
- /* replace Response by Request Authenticator for verification */
- memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
- if (req_auth)
- {
- memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
- }
- else
- {
- memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
- }
msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
- /* verify Response-Authenticator */
- hasher->get_hash(hasher, msg, NULL);
- hasher->get_hash(hasher, secret, buf);
- if (!memeq(buf, res_auth, HASH_SIZE_MD5))
+ if (this->msg->code != RMC_ACCESS_REQUEST)
{
- DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
- return FALSE;
+ /* replace Response by Request Authenticator for verification */
+ memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
+ if (req_auth)
+ {
+ memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
+ }
+ else
+ {
+ memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
+ }
+
+ /* verify Response-Authenticator */
+ hasher->get_hash(hasher, msg, NULL);
+ hasher->get_hash(hasher, secret, buf);
+ if (!memeq(buf, res_auth, HASH_SIZE_MD5))
+ {
+ DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
+ return FALSE;
+ }
}
/* verify Message-Authenticator attribute */
}
}
enumerator->destroy(enumerator);
- /* restore Response-Authenticator */
- memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
+
+ if (this->msg->code != RMC_ACCESS_REQUEST)
+ {
+ /* restore Response-Authenticator */
+ memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
+ }
if (has_eap && !has_auth)
{ /* Message-Authenticator is required if we have an EAP-Message */