/*
- * 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;
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
}
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;
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)
{
status_t status;
exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED;
bool new_mid = FALSE;
+ bool expect_response = FALSE;
if (!this->rng)
{
{
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;
}
case TASK_QUICK_MODE:
exchange = QUICK_MODE;
break;
- case TASK_XAUTH_REQUEST:
+ case TASK_XAUTH:
exchange = TRANSACTION;
new_mid = TRUE;
break;
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:
/* 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);
}
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;
}
host_t *me, *other;
bool delete = FALSE;
status_t status;
- bool migrate = FALSE;
me = request->get_destination(request);
other = request->get_source(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);
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;
+ }
- if (migrate)
+ 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 (task)
+ {
+ /* Let the task build the response */
+ if (task->build(task,response) != SUCCESS)
+ {
+ response->destroy(response);
+ return;
+ }
+ }
+ else
{
- migrate_tasks(this->passive_tasks, this->queued_tasks);
- /* Kick off the newly installed tasks */
- initiate(this);
+ 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);
}
/**
{
enumerator_t *enumerator;
task_t *task = NULL;
+ 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 */
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;
}
/* 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);
}
enumerator->destroy(enumerator);
- return build_response(this, message);
+ if (send_response)
+ {
+ 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 SUCCESS;
}
/**
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)
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)