Queue Mode Config tasks when required
[strongswan.git] / src / libcharon / sa / task_manager_v1.c
old mode 100644 (file)
new mode 100755 (executable)
index eaab362..e899b06
@@ -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 <math.h>
 
 #include <daemon.h>
-#include <sa/tasks/ike_vendor.h>
+#include <sa/tasks/child_delete.h>
 #include <sa/tasks/main_mode.h>
 #include <sa/tasks/quick_mode.h>
-#include <sa/tasks/xauth_request.h>
+#include <sa/tasks/xauth.h>
+#include <sa/tasks/mode_config.h>
+#include <sa/tasks/ike_delete.h>
+#include <sa/tasks/ike_natd_v1.h>
 #include <sa/tasks/ike_vendor_v1.h>
+#include <sa/tasks/ike_cert_pre_v1.h>
+#include <sa/tasks/ike_cert_post_v1.h>
+#include <encoding/payloads/delete_payload.h>
 #include <processing/jobs/retransmit_job.h>
+#include <processing/jobs/delete_ike_sa_job.h>
 
 typedef struct exchange_t exchange_t;
 
@@ -97,9 +104,9 @@ struct private_task_manager_t {
                u_int32_t mid;
 
                /**
-                * Hash of a previously received message
+                * Sequence number of the last sent message
                 */
-               u_int32_t hash;
+               u_int32_t seqnr;
 
                /**
                 * how many times we have retransmitted so far
@@ -147,11 +154,6 @@ struct private_task_manager_t {
         * Base to calculate retransmission timeout
         */
        double retransmit_base;
-
-       /**
-        * Signal to the task manager that we need to initiate a transaction after the response is sent.
-        */
-       bool initiate_later_flag;
 };
 
 /**
@@ -196,9 +198,10 @@ static bool activate_task(private_task_manager_t *this, task_type_t type)
 }
 
 METHOD(task_manager_t, retransmit, status_t,
-       private_task_manager_t *this, u_int32_t message_id)
+       private_task_manager_t *this, u_int32_t message_seqnr)
 {
-       if (message_id == this->initiating.mid)
+       /* this.initiating packet used as marker for received response */
+       if (message_seqnr == this->initiating.seqnr && this->initiating.packet )
        {
                u_int32_t timeout;
                packet_t *packet;
@@ -222,39 +225,20 @@ METHOD(task_manager_t, retransmit, status_t,
 
                if (this->initiating.retransmitted)
                {
-                       DBG1(DBG_IKE, "retransmit %d of request with message ID %d",
-                                this->initiating.retransmitted, message_id);
+                       DBG1(DBG_IKE, "retransmit %d of request with message ID %d seqnr (%d)",
+                                this->initiating.retransmitted, this->initiating.mid, message_seqnr);
                }
                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,
+               job = (job_t*)retransmit_job_create(this->initiating.seqnr,
                                                                                        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)
 {
@@ -265,6 +249,7 @@ METHOD(task_manager_t, initiate, status_t,
        status_t status;
        exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED;
        bool new_mid = FALSE;
+       bool expect_response = FALSE;
 
        if (!this->rng)
        {
@@ -287,21 +272,31 @@ METHOD(task_manager_t, initiate, status_t,
                {
                        case IKE_CREATED:
                                activate_task(this, TASK_VENDOR_V1);
+                               activate_task(this, TASK_IKE_CERT_PRE_V1);
                                if (activate_task(this, TASK_MAIN_MODE))
                                {
                                        exchange = ID_PROT;
+                                       activate_task(this, TASK_IKE_CERT_POST_V1);
+                                       activate_task(this, TASK_IKE_NATD_V1);
+                               }
+                               break;
+                       case IKE_CONNECTING:
+                               if (activate_task(this, TASK_XAUTH))
+                               {
+                                       exchange = TRANSACTION;
+                                       new_mid = TRUE;
                                }
                                break;
                        case IKE_ESTABLISHED:
-                               if (activate_task(this, TASK_QUICK_MODE))
+                               if (activate_task(this, TASK_MODE_CONFIG))
                                {
-                                       exchange = QUICK_MODE;
+                                       exchange = TRANSACTION;
                                        new_mid = TRUE;
                                        break;
                                }
-                               if (activate_task(this, TASK_XAUTH_REQUEST))
+                               if (activate_task(this, TASK_QUICK_MODE))
                                {
-                                       exchange = TRANSACTION;
+                                       exchange = QUICK_MODE;
                                        new_mid = TRUE;
                                        break;
                                }
@@ -325,7 +320,7 @@ METHOD(task_manager_t, initiate, status_t,
                                case TASK_QUICK_MODE:
                                        exchange = QUICK_MODE;
                                        break;
-                               case TASK_XAUTH_REQUEST:
+                               case TASK_XAUTH:
                                        exchange = TRANSACTION;
                                        new_mid = TRUE;
                                        break;
@@ -370,14 +365,8 @@ METHOD(task_manager_t, initiate, status_t,
                                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:
+                               expect_response = TRUE;
                                /* processed, but task needs another exchange */
                                break;
                        case FAILED:
@@ -399,6 +388,7 @@ METHOD(task_manager_t, initiate, status_t,
 
        /* update exchange type if a task changed it */
        this->initiating.type = message->get_exchange_type(message);
+       this->initiating.seqnr++;
 
        status = this->ike_sa->generate_message(this->ike_sa, message,
                                                                                        &this->initiating.packet);
@@ -413,9 +403,14 @@ METHOD(task_manager_t, initiate, status_t,
        }
        message->destroy(message);
 
+       if (expect_response)
+       {
+               return retransmit(this, this->initiating.seqnr);
+       }
        charon->sender->send(charon->sender,
                                this->initiating.packet->clone(this->initiating.packet));
-
+       this->initiating.packet->destroy(this->initiating.packet);
+       this->initiating.packet = NULL;
        return SUCCESS;
 }
 
@@ -438,7 +433,6 @@ 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);
@@ -456,9 +450,6 @@ 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);
@@ -505,19 +496,71 @@ 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 (delete)
+       {
+               return DESTROY_ME;
+       }
+       return SUCCESS;
+}
+
+/**
+ * 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, task_t *task)
+{
+       message_t *response;
+       packet_t *packet;
+       host_t *me, *other;
+       u_int32_t mid;
+
+       if (request && request->get_exchange_type(request) == INFORMATIONAL_V1)
+       {       /* don't respond to INFORMATIONAL requests to avoid a notify war */
+               DBG1(DBG_IKE, "ignore malformed INFORMATIONAL request");
+               return;
+       }
+
+       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);
 
-       if (migrate)
+       if (task)
        {
-               migrate_tasks(this->passive_tasks, this->queued_tasks);
-               /* Kick off the newly installed tasks */
-               initiate(this);
+               /* Let the task build the response */
+               if (task->build(task,response) != SUCCESS)
+               {
+                       response->destroy(response);
+                       return;
+               }
+       }
+       else
+       {
+               response->add_notify(response, FALSE, type, data);
        }
 
-       if (delete)
+       me = this->ike_sa->get_my_host(this->ike_sa);
+       if (me->is_anyaddr(me))
        {
-               return DESTROY_ME;
+               me = request->get_destination(request);
+               this->ike_sa->set_my_host(this->ike_sa, me->clone(me));
        }
-       return SUCCESS;
+       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);
 }
 
 /**
@@ -528,7 +571,9 @@ static status_t process_request(private_task_manager_t *this,
 {
        enumerator_t *enumerator;
        task_t *task = NULL;
-       status_t process_status;
+       bool send_response = FALSE;
+       payload_t *payload;
+       notify_payload_t *notify;
 
        if (this->passive_tasks->get_count(this->passive_tasks) == 0)
        {       /* create tasks depending on request type, if not already some queued */
@@ -537,22 +582,91 @@ static status_t process_request(private_task_manager_t *this,
                        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*)ike_cert_pre_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);
+                               task = (task_t*)ike_cert_post_v1_create(this->ike_sa, FALSE);
+                               this->passive_tasks->insert_last(this->passive_tasks, task);
+                               task = (task_t *)ike_natd_v1_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:
+                               if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED)
+                               {
+                                       DBG1(DBG_IKE, "received quick mode request for "
+                                                "unestablished IKE_SA, ignored");
+                                       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;
+                               enumerator = message->create_payload_enumerator(message);
+                               while (enumerator->enumerate(enumerator, &payload))
+                               {
+                                       switch (payload->get_type(payload))
+                                       {
+                                               case NOTIFY_V1:
+                                               {
+                                                       notify = (notify_payload_t*)payload;
+                                                       switch (notify->get_notify_type(notify))
+                                                       {
+                                                               /* TODO-IKEv1: Add notification types here as needed */
+                                                               case INITIAL_CONTACT_IKEV1:
+                                                                       break;
+                                                               default:
+                                                                       if(notify->get_notify_type(notify) < 16384)
+                                                                       {
+                                                                               DBG1(DBG_IKE, "Received %N error notification.", notify_type_names, notify->get_notify_type(notify));
+                                                                               return FAILED;
+                                                                       }
+                                                                       break;
+                                                       }
+                                                       break;
+                                               }
+                                               case DELETE_V1:
+                                               {
+                                                       delete_payload_t *delete;
+                                                       delete = (delete_payload_t*)payload;
+
+                                                       if (delete->get_protocol_id(delete) == PROTO_IKE)
+                                                       {
+                                                               task = (task_t*)ike_delete_create(this->ike_sa,
+                                                                       FALSE);
+                                                       }
+                                                       else
+                                                       {
+                                                               task = (task_t*)child_delete_create(this->ike_sa,
+                                                                       PROTO_NONE, 0);
+                                                       }
+                                                       break;
+                                               }
+                                               default:
+                                                       break;
+                                       }
+                                       if (task)
+                                       {
+                                               this->passive_tasks->insert_last(this->passive_tasks, task);
+                                       }
+                               }
+                               enumerator->destroy(enumerator);
+                               break;
+                       case TRANSACTION:
+                               if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED)
+                               {
+                                       task = (task_t *)mode_config_create(this->ike_sa, FALSE);
+                               }
+                               else
+                               {
+                                       task = (task_t *)xauth_create(this->ike_sa, FALSE);
+                               }
+                               this->passive_tasks->insert_last(this->passive_tasks, task);
+                               break;
                        default:
                                return FAILED;
                }
@@ -567,21 +681,13 @@ 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);
-                               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;
+                               break;
                        case NEED_MORE:
                                /* processed, but task needs at least another call to build() */
+                               send_response = TRUE;
                                break;
+                       case FAILED_SEND_ERROR:
+                               send_notify_response(this, NULL, 0, chunk_empty, task);
                        case FAILED:
                        default:
                                charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
@@ -596,14 +702,21 @@ static status_t process_request(private_task_manager_t *this,
        }
        enumerator->destroy(enumerator);
 
-       process_status = build_response(this, message);
-
-       if(((process_status == SUCCESS) || (process_status == NEED_MORE)) && (this->initiate_later_flag == TRUE))
+       if (send_response)
        {
-               this->initiate_later_flag = FALSE;
+               if (build_response(this, message) != SUCCESS)
+               {
+                       return DESTROY_ME;
+               }
+       }
+       if (this->passive_tasks->get_count(this->passive_tasks) == 0 &&
+               this->queued_tasks->get_count(this->queued_tasks) > 0)
+       {
+               /* passive tasks completed, check if an active task has been queued,
+                * such as XAUTH or modeconfig push */
                return initiate(this);
        }
-       return process_status;
+       return SUCCESS;
 }
 
 /**
@@ -658,22 +771,83 @@ static status_t process_response(private_task_manager_t *this,
        return initiate(this);
 }
 
+/**
+ * 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, NULL);
+                               break;
+                       case PARSE_ERROR:
+                               DBG1(DBG_IKE, "message parsing failed");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty, NULL);
+                               break;
+                       case VERIFY_ERROR:
+                               DBG1(DBG_IKE, "message verification failed");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty, NULL);
+                               break;
+                       case FAILED:
+                               DBG1(DBG_IKE, "integrity check failed");
+                               send_notify_response(this, msg,
+                                                                        INVALID_HASH_INFORMATION, chunk_empty, NULL);
+                               break;
+                       case INVALID_STATE:
+                               DBG1(DBG_IKE, "found encrypted message, but no keys available");
+                               send_notify_response(this, msg,
+                                                                        PAYLOAD_MALFORMED, chunk_empty, NULL);
+                       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)
 {
        u_int32_t hash, mid;
        host_t *me, *other;
-
-       mid = msg->get_message_id(msg);
+       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)))
        {
+               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)
@@ -693,6 +867,39 @@ METHOD(task_manager_t, process_message, status_t,
                                                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, sending %N",
+                                        me, other, notify_type_names, NO_PROPOSAL_CHOSEN);
+                               send_notify_response(this, msg,
+                                                                        NO_PROPOSAL_CHOSEN, chunk_empty, NULL);
+                               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)
@@ -777,12 +984,6 @@ METHOD(task_manager_t, destroy, void,
        free(this);
 }
 
-METHOD(task_manager_t, initiate_later, void,
-       private_task_manager_t *this)
-{
-       this->initiate_later_flag = TRUE;
-}
-
 /*
  * see header file
  */
@@ -803,7 +1004,6 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                                .busy = _busy,
                                .create_task_enumerator = _create_task_enumerator,
                                .destroy = _destroy,
-                               .initiate_later = _initiate_later,
                        },
                },
                .ike_sa = ike_sa,
@@ -818,7 +1018,6 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                                                                "charon.retransmit_timeout", RETRANSMIT_TIMEOUT),
                .retransmit_base = lib->settings->get_double(lib->settings,
                                                                "charon.retransmit_base", RETRANSMIT_BASE),
-               .initiate_later_flag = FALSE,
        );
 
        return &this->public;