read PDP server name from strongswan.conf
[strongswan.git] / src / libcharon / plugins / tnc_pdp / tnc_pdp.c
index f0cf866..6daae8f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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 <errno.h>
 #include <unistd.h>
 
+#include <radius_message.h>
+
 #include <daemon.h>
 #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;
 
 /**
  * Maximum size of a RADIUS IP packet
  */
-#define MAX_PACKET 2048
+#define MAX_PACKET 4096
 
 /**
  * private data of tnc_pdp_t
@@ -41,6 +44,16 @@ struct private_tnc_pdp_t {
        tnc_pdp_t public;
 
        /**
+        * ID of the server
+        */
+       identification_t *server;
+
+       /**
+        * EAP method type to be used
+        */
+       eap_type_t type;
+
+       /**
         * IPv4 RADIUS socket
         */
        int ipv4;
@@ -55,6 +68,25 @@ struct 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;
 };
 
 
@@ -100,12 +132,12 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
        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;
        }
@@ -113,7 +145,7 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
        /* 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;
        }
@@ -122,12 +154,144 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
 }
 
 /**
+ * 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, TRUE);
+
+       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 *peer;
+                       chunk_t eap_identity;
+
+                       if (message.len < 5)
+                       {
+                               return;
+                       }
+                       eap_identity = chunk_create(message.ptr + 5, message.len - 5);
+                       peer = identification_create_from_data(eap_identity);
+
+                       this->method = charon->eap->create_instance(charon->eap, this->type,
+                                                                                               0, EAP_SERVER, this->server, peer); 
+                       peer->destroy(peer);
+                       if (!this->method)
+                       {
+                               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;
+                                       DESTROY_IF(out);
+                                       out = eap_payload_create_code(EAP_SUCCESS,
+                                                                                                 in->get_identifier(in));
+                                       break;
+                               case FAILED:
+                               default:
+                                       code = RMC_ACCESS_REJECT;
+                                       DESTROY_IF(out);
+                                       out = eap_payload_create_code(EAP_FAILURE,
+                                                                                                 in->get_identifier(in));
+                       }
+               }
+
+               send_response(this, request, code, out, source);
+               in->destroy(in);
+               out->destroy(out);
+       }
+}
+
+/**
  * Process packets received on the RADIUS socket
  */
 static job_requeue_t receive(private_tnc_pdp_t *this)
 {
        while (TRUE)
        {
+               radius_message_t *request;
                char buffer[MAX_PACKET];
                int max_fd = 0, selected = 0, bytes_read = 0;
                fd_set rfds;
@@ -152,7 +316,7 @@ 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)
                {
@@ -187,17 +351,35 @@ static job_requeue_t receive(private_tnc_pdp_t *this)
                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);
+               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_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_CFG, "received invalid RADIUS message, ignored");
+               }
                source->destroy(source);
        }
        return JOB_REQUEUE_FAIR;
@@ -206,7 +388,10 @@ static job_requeue_t receive(private_tnc_pdp_t *this)
 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);
@@ -215,6 +400,10 @@ METHOD(tnc_pdp_t, destroy, void,
        {
                close(this->ipv6);
        }
+       DESTROY_IF(this->server);
+       DESTROY_IF(this->signer);
+       DESTROY_IF(this->hasher);
+       DESTROY_IF(this->method);
        free(this);
 }
 
@@ -224,13 +413,17 @@ METHOD(tnc_pdp_t, destroy, void,
 tnc_pdp_t *tnc_pdp_create(u_int16_t port)
 {
        private_tnc_pdp_t *this;
+       char *secret, *server;
 
        INIT(this,
                .public = {
                        .destroy = _destroy,
                },
+               .type = EAP_TTLS,
                .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)
@@ -247,6 +440,33 @@ tnc_pdp_t *tnc_pdp_create(u_int16_t port)
        {
                DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
        }
+       if (!this->hasher || !this->signer)
+       {
+               destroy(this);
+               return NULL;
+       }
+
+       server = lib->settings->get_str(lib->settings,
+                                               "charon.plugins.tnc-pdp.server", NULL);
+       if (!server)
+       {
+               DBG1(DBG_CFG, "missing PDP server name, PDP disabled");
+               destroy(this);
+               return NULL;
+       }
+       this->server = identification_create_from_string(server);
+
+       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);