Moved generic RADIUS protocol support to a dedicated libradius
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_dae.c
index ce9a2ab..1cc19af 100644 (file)
@@ -15,7 +15,7 @@
 
 #include "eap_radius_dae.h"
 
-#include "radius_message.h"
+#include <radius_message.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -71,9 +71,114 @@ struct private_eap_radius_dae_t {
         * HMAC MD5 signer, with secret set
         */
        signer_t *signer;
+
+       /**
+        * List of responses for retransmission, as entry_t
+        */
+       linked_list_t *responses;
 };
 
 /**
+ * Entry to store responses for retransmit
+ */
+typedef struct {
+       /** stored response */
+       radius_message_t *response;
+       /** client that sent the request */
+       host_t *client;
+} entry_t;
+
+/**
+ * Clean up an entry
+ */
+static void entry_destroy(entry_t *entry)
+{
+       entry->response->destroy(entry->response);
+       entry->client->destroy(entry->client);
+       free(entry);
+}
+
+/**
+ * Save/Replace response for retransmission
+ */
+static void save_retransmit(private_eap_radius_dae_t *this,
+                                                       radius_message_t *response, host_t *client)
+{
+       enumerator_t *enumerator;
+       entry_t *entry;
+       bool found = FALSE;
+
+       enumerator = this->responses->create_enumerator(this->responses);
+       while (enumerator->enumerate(enumerator, &entry))
+       {
+               if (client->equals(client, entry->client))
+               {
+                       entry->response->destroy(entry->response);
+                       entry->response = response;
+                       found = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       if (!found)
+       {
+               INIT(entry,
+                       .response = response,
+                       .client = client->clone(client),
+               );
+               this->responses->insert_first(this->responses, entry);
+       }
+}
+
+/**
+ * Send a RADIUS message to client
+ */
+static void send_message(private_eap_radius_dae_t *this,
+                                                radius_message_t *message, host_t *client)
+{
+       chunk_t data;
+
+       data = message->get_encoding(message);
+       if (sendto(this->fd, data.ptr, data.len, 0, client->get_sockaddr(client),
+                          *client->get_sockaddr_len(client)) != data.len)
+       {
+               DBG1(DBG_CFG, "sending RADIUS DAE response failed: %s", strerror(errno));
+       }
+}
+
+/**
+ * Check if we request is a retransmit, retransmit stored response
+ */
+static bool send_retransmit(private_eap_radius_dae_t *this,
+                                                       radius_message_t *request, host_t *client)
+{
+       enumerator_t *enumerator;
+       entry_t *entry;
+       bool found = FALSE;
+
+       enumerator = this->responses->create_enumerator(this->responses);
+       while (enumerator->enumerate(enumerator, &entry))
+       {
+               if (client->equals(client, entry->client) &&
+                       request->get_identifier(request) ==
+                                                       entry->response->get_identifier(entry->response))
+               {
+                       DBG1(DBG_CFG, "received retransmit of RADIUS %N, retransmitting %N "
+                                "to %H", radius_message_code_names, request->get_code(request),
+                                radius_message_code_names,
+                                entry->response->get_code(entry->response), client);
+                       send_message(this, entry->response, client);
+                       found = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       return found;
+}
+
+/**
  * Send an ACK/NAK response for a request
  */
 static void send_response(private_eap_radius_dae_t *this,
@@ -81,20 +186,14 @@ static void send_response(private_eap_radius_dae_t *this,
                                                  host_t *client)
 {
        radius_message_t *response;
-       chunk_t data;
 
        response = radius_message_create(code);
        response->set_identifier(response, request->get_identifier(request));
        response->sign(response, request->get_authenticator(request),
                                   this->secret, this->hasher, this->signer, NULL);
 
-       data = response->get_encoding(response);
-       if (sendto(this->fd, data.ptr, data.len, 0, client->get_sockaddr(client),
-                          *client->get_sockaddr_len(client)) != data.len)
-       {
-               DBG1(DBG_CFG, "sending RADIUS DAE response failed: %s", strerror(errno));
-       }
-       response->destroy(response);
+       send_message(this, response, client);
+       save_retransmit(this, response, client);
 }
 
 /**
@@ -140,7 +239,8 @@ static linked_list_t *get_matching_ike_sas(private_eap_radius_dae_t *this,
                {
                        user = identification_create_from_data(data);
                        DBG1(DBG_CFG, "received RADIUS DAE %N for %Y from %H",
-                                radius_message_code_names, RMC_DISCONNECT_REQUEST, user, client);
+                                radius_message_code_names, request->get_code(request),
+                                user, client);
                        add_matching_ike_sas(ids, user);
                        user->destroy(user);
                }
@@ -190,6 +290,93 @@ static void process_disconnect(private_eap_radius_dae_t *this,
 }
 
 /**
+ * Apply a new lifetime to an IKE_SA
+ */
+static void apply_lifetime(private_eap_radius_dae_t *this, ike_sa_id_t *id,
+                                                  u_int32_t lifetime)
+{
+       ike_sa_t *ike_sa;
+
+       ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
+       if (ike_sa)
+       {
+               if (ike_sa->set_auth_lifetime(ike_sa, lifetime) == DESTROY_ME)
+               {
+                       charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
+                                                                                                               ike_sa);
+               }
+               else
+               {
+                       charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
+               }
+       }
+}
+
+/**
+ * Process a DAE CoA request, send response
+ */
+static void process_coa(private_eap_radius_dae_t *this,
+                                               radius_message_t *request, host_t *client)
+{
+       enumerator_t *enumerator;
+       linked_list_t *ids;
+       ike_sa_id_t *id;
+       chunk_t data;
+       int type;
+       u_int32_t lifetime = 0;
+       bool lifetime_seen = FALSE;
+
+       ids = get_matching_ike_sas(this, request, client);
+
+       if (ids->get_count(ids))
+       {
+               enumerator = request->create_enumerator(request);
+               while (enumerator->enumerate(enumerator, &type, &data))
+               {
+                       if (type == RAT_SESSION_TIMEOUT && data.len == 4)
+                       {
+                               lifetime = untoh32(data.ptr);
+                               lifetime_seen = TRUE;
+                               break;
+                       }
+               }
+               enumerator->destroy(enumerator);
+
+               if (lifetime_seen)
+               {
+                       DBG1(DBG_CFG, "applying %us lifetime to %d IKE_SA%s matching %N, "
+                                "sending %N", lifetime, ids->get_count(ids),
+                                ids->get_count(ids) > 1 ? "s" : "",
+                                radius_message_code_names, RMC_COA_REQUEST,
+                                radius_message_code_names, RMC_COA_ACK);
+
+                       enumerator = ids->create_enumerator(ids);
+                       while (enumerator->enumerate(enumerator, &id))
+                       {
+                               apply_lifetime(this, id, lifetime);
+                       }
+                       enumerator->destroy(enumerator);
+                       send_response(this, request, RMC_COA_ACK, client);
+               }
+               else
+               {
+                       DBG1(DBG_CFG, "no Session-Timeout attribute found in %N, sending %N",
+                                radius_message_code_names, RMC_COA_REQUEST,
+                                radius_message_code_names, RMC_COA_NAK);
+                       send_response(this, request, RMC_COA_NAK, client);
+               }
+       }
+       else
+       {
+               DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
+                        radius_message_code_names, RMC_COA_REQUEST,
+                        radius_message_code_names, RMC_COA_NAK);
+               send_response(this, request, RMC_COA_NAK, client);
+       }
+       ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
+}
+
+/**
  * Receive RADIUS DAE requests
  */
 static job_requeue_t receive(private_eap_radius_dae_t *this)
@@ -215,21 +402,26 @@ static job_requeue_t receive(private_eap_radius_dae_t *this)
                        client = host_create_from_sockaddr((struct sockaddr*)&addr);
                        if (client)
                        {
-                               if (request->verify(request, NULL, this->secret,
-                                                                       this->hasher, this->signer))
+                               if (!send_retransmit(this, request, client))
                                {
-                                       switch (request->get_code(request))
+                                       if (request->verify(request, NULL, this->secret,
+                                                                               this->hasher, this->signer))
                                        {
-                                               case RMC_DISCONNECT_REQUEST:
-                                                       process_disconnect(this, request, client);
+                                               switch (request->get_code(request))
+                                               {
+                                                       case RMC_DISCONNECT_REQUEST:
+                                                               process_disconnect(this, request, client);
+                                                               break;
+                                                       case RMC_COA_REQUEST:
+                                                               process_coa(this, request, client);
+                                                               break;
+                                                       default:
+                                                               DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE "
+                                                                        "%N message from %H",
+                                                                        radius_message_code_names,
+                                                                        request->get_code(request), client);
                                                        break;
-                                               case RMC_COA_REQUEST:
-                                                       /* TODO */
-                                               default:
-                                                       DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE %N "
-                                                                "message from %H", radius_message_code_names,
-                                                                request->get_code(request), client);
-                                               break;
+                                               }
                                        }
                                }
                                client->destroy(client);
@@ -297,6 +489,7 @@ METHOD(eap_radius_dae_t, destroy, void,
        }
        DESTROY_IF(this->signer);
        DESTROY_IF(this->hasher);
+       this->responses->destroy_function(this->responses, (void*)entry_destroy);
        free(this);
 }
 
@@ -319,6 +512,7 @@ eap_radius_dae_t *eap_radius_dae_create(eap_radius_accounting_t *accounting)
                },
                .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
                .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
+               .responses = linked_list_create(),
        );
 
        if (!this->hasher || !this->signer)