Handle unsupported IKEv1 exchange types more specifically.
[strongswan.git] / src / libcharon / sa / task_manager_v1.c
index 7eb81fc..c1868f2 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2007 Tobias Brunner
- * Copyright (C) 2007-2010 Martin Willi
+ * Copyright (C) 2007-2011 Tobias Brunner
+ * Copyright (C) 2007-2011 Martin Willi
  * Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
 
 #include "task_manager_v1.h"
 
+#include <math.h>
+
 #include <daemon.h>
+#include <sa/tasks/ike_vendor.h>
 #include <sa/tasks/main_mode.h>
+#include <sa/tasks/quick_mode.h>
+#include <sa/tasks/xauth_request.h>
+#include <sa/tasks/ike_vendor_v1.h>
+#include <processing/jobs/retransmit_job.h>
+#include <processing/jobs/delete_ike_sa_job.h>
 
 typedef struct exchange_t exchange_t;
 
@@ -55,6 +63,11 @@ struct private_task_manager_t {
        ike_sa_t *ike_sa;
 
        /**
+        * RNG to create message IDs
+        */
+       rng_t *rng;
+
+       /**
         * Exchange we are currently handling as responder
         */
        struct {
@@ -64,6 +77,11 @@ struct private_task_manager_t {
                u_int32_t mid;
 
                /**
+                * Hash of a previously received message
+                */
+               u_int32_t hash;
+
+               /**
                 * packet for retransmission
                 */
                packet_t *packet;
@@ -80,6 +98,11 @@ struct private_task_manager_t {
                u_int32_t mid;
 
                /**
+                * Hash of a previously received message
+                */
+               u_int32_t hash;
+
+               /**
                 * how many times we have retransmitted so far
                 */
                u_int retransmitted;
@@ -112,11 +135,6 @@ struct private_task_manager_t {
        linked_list_t *passive_tasks;
 
        /**
-        * the task manager has been reset
-        */
-       bool reset;
-
-       /**
         * Number of times we retransmit messages before giving up
         */
        u_int retransmit_tries;
@@ -148,16 +166,253 @@ static void flush(private_task_manager_t *this)
        this->active_tasks = linked_list_create();
 }
 
+/**
+ * move a task of a specific type from the queue to the active list
+ */
+static bool activate_task(private_task_manager_t *this, task_type_t type)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+       bool found = FALSE;
+
+       enumerator = this->queued_tasks->create_enumerator(this->queued_tasks);
+       while (enumerator->enumerate(enumerator, (void**)&task))
+       {
+               if (task->get_type(task) == type)
+               {
+                       DBG2(DBG_IKE, "  activating %N task", task_type_names, type);
+                       this->queued_tasks->remove_at(this->queued_tasks, enumerator);
+                       this->active_tasks->insert_last(this->active_tasks, task);
+                       found = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       return found;
+}
+
 METHOD(task_manager_t, retransmit, status_t,
        private_task_manager_t *this, u_int32_t message_id)
 {
-       return FAILED;
+       if (message_id == this->initiating.mid)
+       {
+               u_int32_t timeout;
+               packet_t *packet;
+               job_t *job;
+
+               if (this->initiating.retransmitted <= this->retransmit_tries)
+               {
+                       timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 *
+                               pow(this->retransmit_base, this->initiating.retransmitted));
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "giving up after %d retransmits",
+                                this->initiating.retransmitted - 1);
+                       if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)
+                       {
+                               charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
+                       }
+                       return DESTROY_ME;
+               }
+
+               if (this->initiating.retransmitted)
+               {
+                       DBG1(DBG_IKE, "retransmit %d of request with message ID %d",
+                                this->initiating.retransmitted, message_id);
+               }
+               packet = this->initiating.packet->clone(this->initiating.packet);
+               charon->sender->send(charon->sender, packet);
+
+               this->initiating.retransmitted++;
+               job = (job_t*)retransmit_job_create(this->initiating.mid,
+                                                                                       this->ike_sa->get_id(this->ike_sa));
+               lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout);
+       }
+       return SUCCESS;
+}
+
+void migrate_tasks(linked_list_t *from, linked_list_t *to)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+
+       enumerator = from->create_enumerator(from);
+       while(enumerator->enumerate(enumerator, (void**)&task))
+       {
+               DBG4(DBG_IKE, "  Migrating %N task to new queue", task_type_names, task->get_type(task));
+               if(task->swap_initiator)
+               {
+                       task->swap_initiator(task);
+               }
+               to->insert_last(to, task);
+               from->remove_at(from, enumerator);
+       }
+       enumerator->destroy(enumerator);
 }
 
 METHOD(task_manager_t, initiate, status_t,
        private_task_manager_t *this)
 {
-       return FAILED;
+       enumerator_t *enumerator;
+       task_t *task;
+       message_t *message;
+       host_t *me, *other;
+       status_t status;
+       exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED;
+       bool new_mid = FALSE;
+
+       if (!this->rng)
+       {
+               DBG1(DBG_IKE, "no RNG supported");
+               return FAILED;
+       }
+
+       if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED)
+       {
+               DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress",
+                               exchange_type_names, this->initiating.type);
+               /* do not initiate if we already have a message in the air */
+               return SUCCESS;
+       }
+
+       if (this->active_tasks->get_count(this->active_tasks) == 0)
+       {
+               DBG2(DBG_IKE, "activating new tasks");
+               switch (this->ike_sa->get_state(this->ike_sa))
+               {
+                       case IKE_CREATED:
+                               activate_task(this, TASK_VENDOR_V1);
+                               if (activate_task(this, TASK_MAIN_MODE))
+                               {
+                                       exchange = ID_PROT;
+                               }
+                               break;
+                       case IKE_ESTABLISHED:
+                               if (activate_task(this, TASK_QUICK_MODE))
+                               {
+                                       exchange = QUICK_MODE;
+                                       new_mid = TRUE;
+                                       break;
+                               }
+                               if (activate_task(this, TASK_XAUTH_REQUEST))
+                               {
+                                       exchange = TRANSACTION;
+                                       new_mid = TRUE;
+                                       break;
+                               }
+                               break;
+                       default:
+                               break;
+               }
+       }
+       else
+       {
+               DBG2(DBG_IKE, "reinitiating already active tasks");
+               enumerator = this->active_tasks->create_enumerator(this->active_tasks);
+               while (enumerator->enumerate(enumerator, (void**)&task))
+               {
+                       DBG2(DBG_IKE, "  %N task", task_type_names, task->get_type(task));
+                       switch (task->get_type(task))
+                       {
+                               case TASK_MAIN_MODE:
+                                       exchange = ID_PROT;
+                                       break;
+                               case TASK_QUICK_MODE:
+                                       exchange = QUICK_MODE;
+                                       break;
+                               case TASK_XAUTH_REQUEST:
+                                       exchange = TRANSACTION;
+                                       new_mid = TRUE;
+                                       break;
+                               default:
+                                       continue;
+                       }
+                       break;
+               }
+               enumerator->destroy(enumerator);
+       }
+
+       if (exchange == EXCHANGE_TYPE_UNDEFINED)
+       {
+               DBG2(DBG_IKE, "nothing to initiate");
+               /* nothing to do yet... */
+               return SUCCESS;
+       }
+
+       me = this->ike_sa->get_my_host(this->ike_sa);
+       other = this->ike_sa->get_other_host(this->ike_sa);
+
+       message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION);
+       if (new_mid)
+       {
+               this->rng->get_bytes(this->rng, sizeof(this->initiating.mid),
+                                                        (void*)&this->initiating.mid);
+       }
+       message->set_message_id(message, this->initiating.mid);
+       message->set_source(message, me->clone(me));
+       message->set_destination(message, other->clone(other));
+       message->set_exchange_type(message, exchange);
+       this->initiating.type = exchange;
+       this->initiating.retransmitted = 0;
+
+       enumerator = this->active_tasks->create_enumerator(this->active_tasks);
+       while (enumerator->enumerate(enumerator, (void*)&task))
+       {
+               switch (task->build(task, message))
+               {
+                       case SUCCESS:
+                               /* task completed, remove it */
+                               this->active_tasks->remove_at(this->active_tasks, enumerator);
+                               task->destroy(task);
+                               break;
+                       case MIGRATE:
+                               /* task completed, remove it */
+                               this->active_tasks->remove_at(this->active_tasks, enumerator);
+                               task->destroy(task);
+                               /* migrate the remaining active tasks to the passive queue */
+                               migrate_tasks(this->active_tasks, this->passive_tasks);
+                               break;
+                       case NEED_MORE:
+                               /* processed, but task needs another exchange */
+                               break;
+                       case FAILED:
+                       default:
+                               if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)
+                               {
+                                       charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
+                               }
+                               /* FALL */
+                       case DESTROY_ME:
+                               /* critical failure, destroy IKE_SA */
+                               enumerator->destroy(enumerator);
+                               message->destroy(message);
+                               flush(this);
+                               return DESTROY_ME;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       /* update exchange type if a task changed it */
+       this->initiating.type = message->get_exchange_type(message);
+
+       status = this->ike_sa->generate_message(this->ike_sa, message,
+                                                                                       &this->initiating.packet);
+       if (status != SUCCESS)
+       {
+               /* message generation failed. There is nothing more to do than to
+                * close the SA */
+               message->destroy(message);
+               flush(this);
+               charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
+               return DESTROY_ME;
+       }
+       message->destroy(message);
+
+       charon->sender->send(charon->sender,
+                               this->initiating.packet->clone(this->initiating.packet));
+
+       return SUCCESS;
 }
 
 /**
@@ -179,6 +434,7 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
        host_t *me, *other;
        bool delete = FALSE;
        status_t status;
+       bool migrate = FALSE;
 
        me = request->get_destination(request);
        other = request->get_source(request);
@@ -188,7 +444,7 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
        /* send response along the path the request came in */
        message->set_source(message, me->clone(me));
        message->set_destination(message, other->clone(other));
-       message->set_message_id(message, this->responding.mid);
+       message->set_message_id(message, request->get_message_id(request));
        message->set_request(message, FALSE);
 
        enumerator = this->passive_tasks->create_enumerator(this->passive_tasks);
@@ -196,6 +452,9 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
        {
                switch (task->build(task, message))
                {
+                       case MIGRATE:
+                               migrate = TRUE;
+                               /* FALL */
                        case SUCCESS:
                                /* task completed, remove it */
                                this->passive_tasks->remove_at(this->passive_tasks, enumerator);
@@ -242,6 +501,14 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
 
        charon->sender->send(charon->sender,
                                                 this->responding.packet->clone(this->responding.packet));
+
+       if (migrate)
+       {
+               migrate_tasks(this->passive_tasks, this->queued_tasks);
+               /* Kick off the newly installed tasks */
+               initiate(this);
+       }
+
        if (delete)
        {
                return DESTROY_ME;
@@ -263,15 +530,21 @@ static status_t process_request(private_task_manager_t *this,
                switch (message->get_exchange_type(message))
                {
                        case ID_PROT:
+                               task = (task_t *)ike_vendor_v1_create(this->ike_sa, FALSE);
+                               this->passive_tasks->insert_last(this->passive_tasks, task);
                                task = (task_t *)main_mode_create(this->ike_sa, FALSE);
                                this->passive_tasks->insert_last(this->passive_tasks, task);
+                               task = (task_t *)xauth_request_create(this->ike_sa, FALSE);
+                               this->passive_tasks->insert_last(this->passive_tasks, task);
                                break;
                        case AGGRESSIVE:
                                /* TODO-IKEv1: agressive mode */
                                return FAILED;
                        case QUICK_MODE:
-                               /* TODO-IKEv1: quick mode */
-                               return FAILED;
+                               task = (task_t *)quick_mode_create(this->ike_sa, NULL,
+                                                                                                  NULL, NULL);
+                               this->passive_tasks->insert_last(this->passive_tasks, task);
+                               break;
                        case INFORMATIONAL_V1:
                                /* TODO-IKEv1: informational */
                                return FAILED;
@@ -289,7 +562,18 @@ static status_t process_request(private_task_manager_t *this,
                                /* task completed, remove it */
                                this->passive_tasks->remove_at(this->passive_tasks, enumerator);
                                task->destroy(task);
-                               break;
+                               enumerator->destroy(enumerator);
+                               return SUCCESS;
+                       case MIGRATE:
+                               /* task completed, remove it */
+                               this->passive_tasks->remove_at(this->passive_tasks, enumerator);
+                               task->destroy(task);
+                               enumerator->destroy(enumerator);
+                               /* migrate the remaining tasks */
+                               migrate_tasks(this->passive_tasks, this->queued_tasks);
+                               /* Kick off the newly installed tasks */
+                               initiate(this);
+                               return SUCCESS;
                        case NEED_MORE:
                                /* processed, but task needs at least another call to build() */
                                break;
@@ -310,15 +594,178 @@ static status_t process_request(private_task_manager_t *this,
        return build_response(this, message);
 }
 
+/**
+ * handle an incoming response message
+ */
+static status_t process_response(private_task_manager_t *this,
+                                                                message_t *message)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+
+       if (message->get_exchange_type(message) != this->initiating.type)
+       {
+               DBG1(DBG_IKE, "received %N response, but expected %N",
+                        exchange_type_names, message->get_exchange_type(message),
+                        exchange_type_names, this->initiating.type);
+               charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
+               return DESTROY_ME;
+       }
+
+       enumerator = this->active_tasks->create_enumerator(this->active_tasks);
+       while (enumerator->enumerate(enumerator, (void*)&task))
+       {
+               switch (task->process(task, message))
+               {
+                       case SUCCESS:
+                               /* task completed, remove it */
+                               this->active_tasks->remove_at(this->active_tasks, enumerator);
+                               task->destroy(task);
+                               break;
+                       case NEED_MORE:
+                               /* processed, but task needs another exchange */
+                               break;
+                       case FAILED:
+                       default:
+                               charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
+                               /* FALL */
+                       case DESTROY_ME:
+                               /* critical failure, destroy IKE_SA */
+                               this->active_tasks->remove_at(this->active_tasks, enumerator);
+                               enumerator->destroy(enumerator);
+                               task->destroy(task);
+                               return DESTROY_ME;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
+       this->initiating.packet->destroy(this->initiating.packet);
+       this->initiating.packet = NULL;
+
+       return initiate(this);
+}
+
+/**
+ * Send a notify in a separate INFORMATIONAL exchange back to the sender.
+ */
+static void send_notify_response(private_task_manager_t *this,
+                                                                message_t *request, notify_type_t type,
+                                                                chunk_t data)
+{
+       message_t *response;
+       packet_t *packet;
+       host_t *me, *other;
+       u_int32_t mid;
+
+       response = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION);
+       response->set_exchange_type(response, INFORMATIONAL_V1);
+       response->set_request(response, TRUE);
+       this->rng->get_bytes(this->rng, sizeof(mid), (void*)&mid);
+       response->set_message_id(response, mid);
+       response->add_notify(response, FALSE, type, data);
+       me = this->ike_sa->get_my_host(this->ike_sa);
+       if (me->is_anyaddr(me))
+       {
+               me = request->get_destination(request);
+               this->ike_sa->set_my_host(this->ike_sa, me->clone(me));
+       }
+       other = this->ike_sa->get_other_host(this->ike_sa);
+       if (other->is_anyaddr(other))
+       {
+               other = request->get_source(request);
+               this->ike_sa->set_other_host(this->ike_sa, other->clone(other));
+       }
+       response->set_source(response, me->clone(me));
+       response->set_destination(response, other->clone(other));
+       if (this->ike_sa->generate_message(this->ike_sa, response,
+                                                                          &packet) == SUCCESS)
+       {
+               charon->sender->send(charon->sender, packet);
+       }
+       response->destroy(response);
+}
+
+/**
+ * Parse the given message and verify that it is valid.
+ */
+static status_t parse_message(private_task_manager_t *this, message_t *msg)
+{
+       status_t status;
+
+       status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa));
+
+       if (status != SUCCESS)
+       {
+               switch (status)
+               {
+                       case NOT_SUPPORTED:
+                               DBG1(DBG_IKE, "unsupported exchange type");
+                               send_notify_response(this, msg,
+                                                                        INVALID_EXCHANGE_TYPE, chunk_empty);
+                               break;
+                       case PARSE_ERROR:
+                               DBG1(DBG_IKE, "message parsing failed");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty);
+                               break;
+                       case VERIFY_ERROR:
+                               DBG1(DBG_IKE, "message verification failed");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty);
+                               break;
+                       case FAILED:
+                               DBG1(DBG_IKE, "integrity check failed");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty);
+                               break;
+                       case INVALID_STATE:
+                               DBG1(DBG_IKE, "found encrypted message, but no keys available");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty);
+                       default:
+                               break;
+               }
+               DBG1(DBG_IKE, "%N %s with message ID %d processing failed",
+                        exchange_type_names, msg->get_exchange_type(msg),
+                        msg->get_request(msg) ? "request" : "response",
+                        msg->get_message_id(msg));
+
+               if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED)
+               {       /* invalid initiation attempt, close SA */
+                       return DESTROY_ME;
+               }
+       }
+       return status;
+}
+
 METHOD(task_manager_t, process_message, status_t,
        private_task_manager_t *this, message_t *msg)
 {
-       /* TODO-IKEv1: detect request/response */
-       if (TRUE)
+       u_int32_t hash, mid;
+       host_t *me, *other;
+       status_t status;
+
+       /* TODO-IKEv1: update hosts more selectively */
+       me = msg->get_destination(msg);
+       other = msg->get_source(msg);
+       mid = msg->get_message_id(msg);
+
+       if ((mid && mid == this->initiating.mid) ||
+               (this->initiating.mid == 0 &&
+                this->active_tasks->get_count(this->active_tasks)))
        {
-               /* TODO-IKEv1: detect mainmode retransmission */
-               charon->bus->message(charon->bus, msg, TRUE);
-               if (process_request(this, msg) != SUCCESS)
+               msg->set_request(msg, FALSE);
+               status = parse_message(this, msg);
+               if (status != SUCCESS)
+               {
+                       return status;
+               }
+               this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+                                                                       time_monotonic(NULL));
+               this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE);
+               charon->bus->message(charon->bus, msg, FALSE);
+               if (process_response(this, msg) != SUCCESS)
                {
                        flush(this);
                        return DESTROY_ME;
@@ -326,8 +773,55 @@ METHOD(task_manager_t, process_message, status_t,
        }
        else
        {
-               /* TODO-IKEv1: handle response */
-               return DESTROY_ME;
+               hash = chunk_hash(msg->get_packet_data(msg));
+               if (hash == this->responding.hash)
+               {
+                       DBG1(DBG_IKE, "received retransmit of request with ID %d, "
+                                "retransmitting response", mid);
+                       charon->sender->send(charon->sender,
+                                               this->responding.packet->clone(this->responding.packet));
+                       return SUCCESS;
+               }
+               msg->set_request(msg, TRUE);
+               status = parse_message(this, msg);
+               if (status != SUCCESS)
+               {
+                       return status;
+               }
+               /* if this IKE_SA is virgin, we check for a config */
+               if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL)
+               {
+                       ike_sa_id_t *ike_sa_id;
+                       ike_cfg_t *ike_cfg;
+                       job_t *job;
+                       ike_cfg = charon->backends->get_ike_cfg(charon->backends, me, other);
+                       if (ike_cfg == NULL)
+                       {
+                               /* no config found for these hosts, destroy */
+                               DBG1(DBG_IKE, "no IKE config found for %H...%H", me, other);
+                               return DESTROY_ME;
+                       }
+                       this->ike_sa->set_ike_cfg(this->ike_sa, ike_cfg);
+                       ike_cfg->destroy(ike_cfg);
+                       /* add a timeout if peer does not establish it completely */
+                       ike_sa_id = this->ike_sa->get_id(this->ike_sa);
+                       job = (job_t*)delete_ike_sa_job_create(ike_sa_id, FALSE);
+                       lib->scheduler->schedule_job(lib->scheduler, job,
+                                       lib->settings->get_int(lib->settings,
+                                               "charon.half_open_timeout",  HALF_OPEN_IKE_SA_TIMEOUT));
+               }
+               this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+                                                                       time_monotonic(NULL));
+               this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE);
+               charon->bus->message(charon->bus, msg, TRUE);
+               if (process_request(this, msg) != SUCCESS)
+               {
+                       flush(this);
+                       return DESTROY_ME;
+               }
+
+               this->responding.mid = mid;
+               this->responding.hash = hash;
        }
        return SUCCESS;
 }
@@ -364,20 +858,11 @@ METHOD(task_manager_t, busy, bool,
 METHOD(task_manager_t, incr_mid, void,
        private_task_manager_t *this, bool initiate)
 {
-       if (initiate)
-       {
-               this->initiating.mid++;
-       }
-       else
-       {
-               this->responding.mid++;
-       }
 }
 
 METHOD(task_manager_t, reset, void,
        private_task_manager_t *this, u_int32_t initiate, u_int32_t respond)
 {
-
 }
 
 METHOD(task_manager_t, create_task_enumerator, enumerator_t*,
@@ -407,6 +892,7 @@ METHOD(task_manager_t, destroy, void,
 
        DESTROY_IF(this->responding.packet);
        DESTROY_IF(this->initiating.packet);
+       DESTROY_IF(this->rng);
        free(this);
 }
 
@@ -434,6 +920,7 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                },
                .ike_sa = ike_sa,
                .initiating.type = EXCHANGE_TYPE_UNDEFINED,
+               .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
                .queued_tasks = linked_list_create(),
                .active_tasks = linked_list_create(),
                .passive_tasks = linked_list_create(),