/*
 * Copyright (C) 2008-2025 Tobias Brunner
 * Copyright (C) 2005-2008 Martin Willi
 * Copyright (C) 2005 Jan Hutter
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <unistd.h>

#include "child_create.h"

#include <daemon.h>
#include <sa/ikev2/keymat_v2.h>
#include <crypto/key_exchange.h>
#include <credentials/certificates/x509.h>
#include <collections/hashtable.h>
#include <encoding/payloads/sa_payload.h>
#include <encoding/payloads/ke_payload.h>
#include <encoding/payloads/ts_payload.h>
#include <encoding/payloads/nonce_payload.h>
#include <encoding/payloads/notify_payload.h>
#include <encoding/payloads/delete_payload.h>
#include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/inactivity_job.h>
#include <processing/jobs/initiate_tasks_job.h>

/** Maximum number of key exchanges (including the initial one, if any) */
#define MAX_KEY_EXCHANGES (MAX_ADDITIONAL_KEY_EXCHANGES + 1)

typedef struct private_child_create_t private_child_create_t;

/** Assumed minimum CPU ID, used when searching for a CPU without SA */
#define CPU_ID_MIN 0

/**
 * Flags for IP-TFS
 */
typedef enum {
	/** Indicates that fragmentation across packets is not supported */
	IPTFS_DONT_FRAGMENT = (1 << 0),
	/** Requests periodic congestion control information from the peer */
	IPTFS_CONGESTION_CONTROL = (1 << 1),
} iptfs_flags_t;

/**
 * Private members of a child_create_t task.
 */
struct private_child_create_t {

	/**
	 * Public methods and task_t interface.
	 */
	child_create_t public;

	/**
	 * Assigned IKE_SA.
	 */
	ike_sa_t *ike_sa;

	/**
	 * Are we the initiator?
	 */
	bool initiator;

	/**
	 * nonce chosen by us
	 */
	chunk_t my_nonce;

	/**
	 * nonce chosen by peer
	 */
	chunk_t other_nonce;

	/**
	 * nonce generator
	 */
	nonce_gen_t *nonceg;

	/**
	 * config to create the CHILD_SA from
	 */
	child_cfg_t *config;

	/**
	 * list of proposal candidates
	 */
	linked_list_t *proposals;

	/**
	 * selected proposal to use for CHILD_SA
	 */
	proposal_t *proposal;

	/**
	 * traffic selectors for initiator side
	 */
	linked_list_t *tsi;

	/**
	 * traffic selectors for responder side
	 */
	linked_list_t *tsr;

	/**
	 * labels for initiator side
	 */
	linked_list_t *labels_i;

	/**
	 * labels for responder side
	 */
	linked_list_t *labels_r;

	/**
	 * source of triggering packet
	 */
	traffic_selector_t *packet_tsi;

	/**
	 * destination of triggering packet
	 */
	traffic_selector_t *packet_tsr;

	/**
	 * Local traffic selectors as configured or previously negotiated
	 */
	traffic_selector_list_t *my_ts;

	/**
	 * Remote traffic selectors as configured or previously negotiated
	 */
	traffic_selector_list_t *other_ts;

	/**
	 * Resource info sent by the peer
	 */
	chunk_t resource_info;

	/**
	 * Whether we received resource info (might be empty)
	 */
	bool resource_info_seen;

	/**
	 * Key exchanges to perform
	 */
	struct {
		transform_type_t type;
		key_exchange_method_t method;
		bool done;
	} key_exchanges[MAX_KEY_EXCHANGES];

	/**
	 * Current key exchange
	 */
	int ke_index;

	/**
	 * Kex exchange method from the parsed or sent KE payload
	 */
	key_exchange_method_t ke_method;

	/**
	 * Current key exchange object (if any)
	 */
	key_exchange_t *ke;

	/**
	 * All key exchanges performed (key_exchange_t)
	 */
	array_t *kes;

	/**
	 * Applying KE public key failed?
	 */
	bool ke_failed;

	/**
	 * Link value for current key exchange
	 */
	chunk_t link;

	/**
	 * IKE_SAs keymat
	 */
	keymat_v2_t *keymat;

	/**
	 * mode the new CHILD_SA uses (transport/tunnel/beet)
	 */
	ipsec_mode_t mode;

	/**
	 * peer accepts TFC padding for this SA
	 */
	bool tfcv3;

	/**
	 * IPComp transform to use
	 */
	ipcomp_transform_t ipcomp;

	/**
	 * IPComp transform proposed or accepted by the other peer
	 */
	ipcomp_transform_t ipcomp_received;

	/**
	 * IP-TFS flags received from the peer
	 */
	uint8_t iptfs_flags;

	/**
	 * IPsec protocol
	 */
	protocol_id_t proto;

	/**
	 * Own allocated SPI
	 */
	uint32_t my_spi;

	/**
	 * SPI received in proposal
	 */
	uint32_t other_spi;

	/**
	 * Own allocated Compression Parameter Index (CPI)
	 */
	uint16_t my_cpi;

	/**
	 * Other Compression Parameter Index (CPI), received via IPCOMP_SUPPORTED
	 */
	uint16_t other_cpi;

	/**
	 * Data collected to create the CHILD_SA
	 */
	child_sa_create_t child;

	/**
	 * CHILD_SA which gets established
	 */
	child_sa_t *child_sa;

	/**
	 * successfully established the CHILD?
	 */
	bool established;

	/**
	 * whether the CHILD_SA rekeys an existing one
	 */
	bool rekey;

	/**
	 * whether we are retrying with another KE method
	 */
	bool retry;

	/**
	 * Whether the task was aborted
	 */
	bool aborted;
};

/**
 * Schedule a retry if creating the CHILD_SA temporary failed
 */
static void schedule_delayed_retry(private_child_create_t *this)
{
	child_create_t *task;
	uint32_t retry;

	retry = RETRY_INTERVAL - (random() % RETRY_JITTER);

	task = child_create_create(this->ike_sa,
							   this->config->get_ref(this->config), FALSE,
							   this->packet_tsi, this->packet_tsr,
							   this->child.seq);
	task->use_reqid(task, this->child.reqid);
	task->use_marks(task, this->child.mark_in, this->child.mark_out);
	task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out);
	task->use_label(task, this->child.label);
	task->use_per_cpu(task, this->child.per_cpu, this->child.cpu);

	/* clone these directly as we don't have a public method */
	if (this->my_ts && this->other_ts)
	{
		private_child_create_t *priv = (private_child_create_t*)task;

		priv->my_ts = this->my_ts->clone(this->my_ts);
		priv->other_ts = this->other_ts->clone(this->other_ts);
	}

	DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds",
		 retry);
	this->ike_sa->queue_task_delayed(this->ike_sa, (task_t*)task, retry);
}

/**
 * get the nonce from a message
 */
static status_t get_nonce(message_t *message, chunk_t *nonce)
{
	nonce_payload_t *payload;

	payload = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
	if (payload == NULL)
	{
		return FAILED;
	}
	*nonce = payload->get_nonce(payload);
	return NEED_MORE;
}

/**
 * generate a new nonce to include in a CREATE_CHILD_SA message
 */
static bool generate_nonce(private_child_create_t *this)
{
	this->nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat);
	if (!this->nonceg)
	{
		DBG1(DBG_IKE, "no nonce generator found to create nonce");
		return FALSE;
	}
	if (!this->nonceg->allocate_nonce(this->nonceg, NONCE_SIZE,
									  &this->my_nonce))
	{
		DBG1(DBG_IKE, "nonce allocation failed");
		return FALSE;
	}
	return TRUE;
}

/**
 * Check a list of traffic selectors if any selector belongs to host
 */
static bool ts_list_is_host(linked_list_t *list, host_t *host)
{
	traffic_selector_t *ts;
	bool is_host = TRUE;
	enumerator_t *enumerator = list->create_enumerator(list);

	while (is_host && enumerator->enumerate(enumerator, (void**)&ts))
	{
		is_host = is_host && ts->is_host(ts, host);
	}
	enumerator->destroy(enumerator);
	return is_host;
}

/**
 * Allocate local SPI
 */
static bool allocate_spi(private_child_create_t *this)
{
	proposal_t *proposal;

	if (this->initiator)
	{
		this->proto = PROTO_ESP;
		/* we just get a SPI for the first protocol. TODO: If we ever support
		 * proposal lists with mixed protocols, we'd need multiple SPIs */
		if (this->proposals->get_first(this->proposals,
									   (void**)&proposal) == SUCCESS)
		{
			this->proto = proposal->get_protocol(proposal);
		}
	}
	else
	{
		this->proto = this->proposal->get_protocol(this->proposal);
	}
	this->my_spi = this->child_sa->alloc_spi(this->child_sa, this->proto);
	if (!this->my_spi)
	{
		DBG1(DBG_IKE, "unable to allocate SPI from kernel");
	}
	return this->my_spi != 0;
}

/**
 * Update the proposals with the allocated SPIs as initiator and check the KE
 * method and promote it if necessary
 */
static bool update_and_check_proposals(private_child_create_t *this)
{
	enumerator_t *enumerator;
	proposal_t *proposal;
	linked_list_t *other_ke_methods;
	bool found = FALSE;

	other_ke_methods = linked_list_create();
	enumerator = this->proposals->create_enumerator(this->proposals);
	while (enumerator->enumerate(enumerator, &proposal))
	{
		proposal->set_spi(proposal, this->my_spi);

		/* move the selected KE method to the front, if any */
		if (this->ke_method != KE_NONE)
		{	/* proposals that don't contain the selected group are
			 * moved to the back */
			if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
											 this->ke_method))
			{
				this->proposals->remove_at(this->proposals, enumerator);
				other_ke_methods->insert_last(other_ke_methods, proposal);
			}
			else
			{
				found = TRUE;
			}
		}
	}
	enumerator->destroy(enumerator);
	enumerator = other_ke_methods->create_enumerator(other_ke_methods);
	while (enumerator->enumerate(enumerator, (void**)&proposal))
	{	/* no need to remove from the list as we destroy it anyway*/
		this->proposals->insert_last(this->proposals, proposal);
	}
	enumerator->destroy(enumerator);
	other_ke_methods->destroy(other_ke_methods);

	return this->ke_method == KE_NONE || found;
}

/**
 * Schedule inactivity timeout for CHILD_SA with reqid, if enabled
 */
static void schedule_inactivity_timeout(private_child_create_t *this)
{
	uint32_t timeout, id;
	bool close_ike;

	timeout = this->config->get_inactivity(this->config);
	if (timeout)
	{
		close_ike = lib->settings->get_bool(lib->settings,
									"%s.inactivity_close_ike", FALSE, lib->ns);
		id = this->child_sa->get_unique_id(this->child_sa);
		lib->scheduler->schedule_job(lib->scheduler, (job_t*)
						inactivity_job_create(id, timeout, close_ike), timeout);
	}
}

/**
 * Substitute any host address with NATed address in traffic selector
 */
static linked_list_t* get_transport_nat_ts(private_child_create_t *this,
										   bool local, linked_list_t *in)
{
	enumerator_t *enumerator;
	linked_list_t *out;
	traffic_selector_t *ts;
	host_t *ike, *first = NULL;
	uint8_t mask;

	if (local)
	{
		ike = this->ike_sa->get_my_host(this->ike_sa);
	}
	else
	{
		ike = this->ike_sa->get_other_host(this->ike_sa);
	}

	out = linked_list_create();

	enumerator = in->create_enumerator(in);
	while (enumerator->enumerate(enumerator, &ts))
	{
		/* require that all selectors match the first "host" selector */
		if (ts->is_host(ts, first))
		{
			if (!first)
			{
				ts->to_subnet(ts, &first, &mask);
			}
			ts = ts->clone(ts);
			ts->set_address(ts, ike);
			out->insert_last(out, ts);
		}
	}
	enumerator->destroy(enumerator);
	DESTROY_IF(first);

	return out;
}

/**
 * Ensure we have traffic selector lists when not recreating an SA.
 */
static void ensure_ts_lists(private_child_create_t *this)
{
	linked_list_t *ts;

	if (!this->my_ts)
	{
		ts = this->config->get_traffic_selectors(this->config, TRUE, NULL);
		this->my_ts = traffic_selector_list_create_from_list(ts);
	}
	if (!this->other_ts)
	{
		ts = this->config->get_traffic_selectors(this->config, FALSE, NULL);
		this->other_ts = traffic_selector_list_create_from_list(ts);
	}
}

/**
 * Narrow received traffic selectors with configuration
 */
static linked_list_t* narrow_ts(private_child_create_t *this, bool local,
								linked_list_t *in)
{
	traffic_selector_list_t *ts;
	linked_list_t *hosts, *nat, *result;
	ike_condition_t cond;

	ensure_ts_lists(this);
	ts = local ? this->my_ts : this->other_ts;
	cond = local ? COND_NAT_HERE : COND_NAT_THERE;
	hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);

	if (this->mode == MODE_TRANSPORT &&
		this->ike_sa->has_condition(this->ike_sa, cond))
	{
		nat = get_transport_nat_ts(this, local, in);
		result = child_cfg_select_ts(this->config, local, ts, nat, hosts);
		nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy));
	}
	else
	{
		result = child_cfg_select_ts(this->config, local, ts, in, hosts);
	}

	hosts->destroy(hosts);
	return result;
}

/**
 * Check if requested mode is acceptable
 */
static bool check_mode(private_child_create_t *this, host_t *i, host_t *r)
{
	switch (this->mode)
	{
		case MODE_TRANSPORT:
			if (!this->config->has_option(this->config, OPT_PROXY_MODE) &&
				   (!ts_list_is_host(this->tsi, i) ||
					!ts_list_is_host(this->tsr, r))
			   )
			{
				DBG1(DBG_IKE, "not using transport mode, not host-to-host");
				return FALSE;
			}
			if (this->config->get_mode(this->config) != MODE_TRANSPORT)
			{
				return FALSE;
			}
			break;
		case MODE_BEET:
			if (!ts_list_is_host(this->tsi, NULL) ||
				!ts_list_is_host(this->tsr, NULL))
			{
				DBG1(DBG_IKE, "not using BEET mode, not host-to-host");
				return FALSE;
			}
			if (this->config->get_mode(this->config) != MODE_BEET)
			{
				return FALSE;
			}
			break;
		case MODE_IPTFS:
			if (this->config->get_mode(this->config) != MODE_IPTFS)
			{
				return FALSE;
			}
			if ((this->iptfs_flags & IPTFS_CONGESTION_CONTROL) ||
				(this->iptfs_flags & 0xfc))
			{
				/* congestion control is not supported, neither are any of
				 * the 6 reserved flags, so we disable IP-TFS if they are set */
				DBG1(DBG_IKE, "not using IP-TFS mode, peer sent unsupported "
					 "flags (0x%.2x) in %N notify", this->iptfs_flags,
					 notify_type_names, USE_AGGFRAG);
				return FALSE;
			}
			break;
		default:
			break;
	}
	return TRUE;
}

/**
 * Do traffic selector narrowing and check mode:
 * - FAILED: mode mismatch
 * - NOT_FOUND: TS unacceptable
 */
static status_t narrow_and_check_ts(private_child_create_t *this, bool ike_auth)
{
	linked_list_t *my_ts, *other_ts;
	host_t *me, *other;

	me = this->ike_sa->get_my_host(this->ike_sa);
	other = this->ike_sa->get_other_host(this->ike_sa);

	this->child_sa->set_proposal(this->child_sa, this->proposal);

	if (this->initiator)
	{
		my_ts = narrow_ts(this, TRUE, this->tsi);
		other_ts = narrow_ts(this, FALSE, this->tsr);
	}
	else
	{
		my_ts = narrow_ts(this, TRUE, this->tsr);
		other_ts = narrow_ts(this, FALSE, this->tsi);
	}

	if (this->initiator)
	{
		if (ike_auth)
		{
			charon->bus->narrow(charon->bus, this->child_sa,
								NARROW_INITIATOR_POST_NOAUTH, my_ts, other_ts);
		}
		else
		{
			charon->bus->narrow(charon->bus, this->child_sa,
								NARROW_INITIATOR_POST_AUTH, my_ts, other_ts);
		}
	}
	else
	{
		charon->bus->narrow(charon->bus, this->child_sa,
							NARROW_RESPONDER, my_ts, other_ts);
	}

	if (my_ts->get_count(my_ts) == 0 || other_ts->get_count(other_ts) == 0)
	{
		charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi, this->tsr);
		my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
		other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
		DBG1(DBG_IKE, "no acceptable traffic selectors found");
		return NOT_FOUND;
	}

	this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
	this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));

	if (this->initiator)
	{
		this->tsi = my_ts;
		this->tsr = other_ts;

		if (!check_mode(this, me, other))
		{
			DBG1(DBG_IKE, "%N mode requested by responder is unacceptable",
				 ipsec_mode_names, this->mode);
			return FAILED;
		}
	}
	else
	{
		this->tsr = my_ts;
		this->tsi = other_ts;

		if (!check_mode(this, other, me))
		{
			this->mode = MODE_TUNNEL;
		}
	}
	return SUCCESS;
}

/**
 * Install a CHILD_SA:
 * - FAILED: failure to install SAs
 * - NOT_FOUND: TS unacceptable (only responder), or failure to install policies
 */
static status_t install_child_sa(private_child_create_t *this)
{
	status_t status, status_i, status_o;
	chunk_t nonce_i, nonce_r;
	chunk_t encr_i = chunk_empty, encr_r = chunk_empty;
	chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
	linked_list_t *my_ts, *other_ts;

	if (this->initiator)
	{
		nonce_i = this->my_nonce;
		nonce_r = this->other_nonce;

		my_ts = this->tsi;
		other_ts = this->tsr;
	}
	else
	{
		nonce_i = this->other_nonce;
		nonce_r = this->my_nonce;

		/* use a copy of the traffic selectors, as the POST hook should not
		 * change payloads */
		my_ts = this->tsr->clone_offset(this->tsr,
										offsetof(traffic_selector_t, clone));
		other_ts = this->tsi->clone_offset(this->tsi,
										offsetof(traffic_selector_t, clone));
		charon->bus->narrow(charon->bus, this->child_sa,
							NARROW_RESPONDER_POST, my_ts, other_ts);

		if (my_ts->get_count(my_ts) == 0 ||	other_ts->get_count(other_ts) == 0)
		{
			my_ts->destroy_offset(my_ts,
								  offsetof(traffic_selector_t, destroy));
			other_ts->destroy_offset(other_ts,
								  offsetof(traffic_selector_t, destroy));
			return NOT_FOUND;
		}
	}

	this->child_sa->set_ipcomp(this->child_sa, this->ipcomp);
	this->child_sa->set_mode(this->child_sa, this->mode);
	if (this->mode == MODE_IPTFS && this->iptfs_flags & IPTFS_DONT_FRAGMENT)
	{
		this->child_sa->set_iptfs_dont_fragment(this->child_sa);
	}
	this->child_sa->set_protocol(this->child_sa,
								 this->proposal->get_protocol(this->proposal));

	/* addresses might have changed since we originally sent the request, update
	 * them before we configure any policies and install the SAs */
	this->child_sa->update(this->child_sa,
						   this->ike_sa->get_my_host(this->ike_sa),
						   this->ike_sa->get_other_host(this->ike_sa), NULL,
						   this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY));

	this->child_sa->set_policies(this->child_sa, my_ts, other_ts);
	if (!this->initiator)
	{
		my_ts->destroy_offset(my_ts,
							  offsetof(traffic_selector_t, destroy));
		other_ts->destroy_offset(other_ts,
							  offsetof(traffic_selector_t, destroy));
	}
	this->child_sa->set_state(this->child_sa, CHILD_INSTALLING);

	if (this->my_cpi == 0 || this->other_cpi == 0 || this->ipcomp == IPCOMP_NONE)
	{
		this->my_cpi = this->other_cpi = 0;
		this->ipcomp = IPCOMP_NONE;
	}
	status_i = status_o = FAILED;
	if (this->keymat->derive_child_keys(this->keymat, this->proposal,
			this->kes, nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r))
	{
		if (this->initiator)
		{
			status_i = this->child_sa->install(this->child_sa, encr_r, integ_r,
							this->my_spi, this->my_cpi, this->initiator,
							TRUE, this->tfcv3);
		}
		else
		{
			status_i = this->child_sa->install(this->child_sa, encr_i, integ_i,
							this->my_spi, this->my_cpi, this->initiator,
							TRUE, this->tfcv3);
		}
		if (this->rekey)
		{
			/* during rekeyings we install the outbound SA and/or policies
			 * separately: as responder, when we receive the delete for the old
			 * SA, as initiator, pretty much immediately in the ike-rekey task,
			 * unless there was a rekey collision that we lost */
			if (this->initiator)
			{
				status_o = this->child_sa->register_outbound(this->child_sa,
							encr_i, integ_i, this->other_spi, this->other_cpi,
							this->initiator, this->tfcv3);
			}
			else
			{
				status_o = this->child_sa->register_outbound(this->child_sa,
							encr_r, integ_r, this->other_spi, this->other_cpi,
							this->initiator, this->tfcv3);
			}
		}
		else if (this->initiator)
		{
			status_o = this->child_sa->install(this->child_sa, encr_i, integ_i,
							this->other_spi, this->other_cpi, this->initiator,
							FALSE, this->tfcv3);
		}
		else
		{
			status_o = this->child_sa->install(this->child_sa, encr_r, integ_r,
							this->other_spi, this->other_cpi, this->initiator,
							FALSE, this->tfcv3);
		}
	}

	if (status_i != SUCCESS || status_o != SUCCESS)
	{
		DBG1(DBG_IKE, "unable to install %s%s%sIPsec SA (SAD) in kernel",
			(status_i != SUCCESS) ? "inbound " : "",
			(status_i != SUCCESS && status_o != SUCCESS) ? "and ": "",
			(status_o != SUCCESS) ? "outbound " : "");
		charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
						   this->child_sa);
		status = FAILED;
	}
	else
	{
		status = this->child_sa->install_policies(this->child_sa);

		if (status != SUCCESS)
		{
			DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel");
			charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_POLICY_FAILED,
							   this->child_sa);
			status = NOT_FOUND;
		}
		else
		{
			charon->bus->child_derived_keys(charon->bus, this->child_sa,
											this->initiator, encr_i, encr_r,
											integ_i, integ_r);
			charon->bus->child_keys(charon->bus, this->child_sa,
								this->initiator, this->kes, nonce_i, nonce_r);
		}
	}
	chunk_clear(&integ_i);
	chunk_clear(&integ_r);
	chunk_clear(&encr_i);
	chunk_clear(&encr_r);

	if (status != SUCCESS)
	{
		return status;
	}

#if DEBUG_LEVEL >= 0
	child_sa_outbound_state_t out_state;

	out_state = this->child_sa->get_outbound_state(this->child_sa);
	my_ts = linked_list_create_from_enumerator(
				this->child_sa->create_ts_enumerator(this->child_sa, TRUE));
	other_ts = linked_list_create_from_enumerator(
				this->child_sa->create_ts_enumerator(this->child_sa, FALSE));

	DBG0(DBG_IKE, "%sCHILD_SA %s{%u} established "
		 "with SPIs %.8x_i %.8x_o and TS %#R === %#R",
		 (out_state == CHILD_OUTBOUND_INSTALLED) ? "" : "inbound ",
		 this->child_sa->get_name(this->child_sa),
		 this->child_sa->get_unique_id(this->child_sa),
		 ntohl(this->child_sa->get_spi(this->child_sa, TRUE)),
		 ntohl(this->child_sa->get_spi(this->child_sa, FALSE)),
		 my_ts, other_ts);

	my_ts->destroy(my_ts);
	other_ts->destroy(other_ts);
#endif

	this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
	this->ike_sa->add_child_sa(this->ike_sa, this->child_sa);
	this->established = TRUE;

	schedule_inactivity_timeout(this);
	return SUCCESS;
}

/**
 * Select a proposal
 */
static bool select_proposal(private_child_create_t *this, bool no_ke)
{
	proposal_selection_flag_t flags = 0;

	if (!this->proposals)
	{
		DBG1(DBG_IKE, "SA payload missing in message");
		return FALSE;
	}

	if (no_ke)
	{
		flags |= PROPOSAL_SKIP_KE;
	}
	if (!this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) &&
		!lib->settings->get_bool(lib->settings, "%s.accept_private_algs",
								 FALSE, lib->ns))
	{
		flags |= PROPOSAL_SKIP_PRIVATE;
	}
	if (!lib->settings->get_bool(lib->settings,
							"%s.prefer_configured_proposals", TRUE, lib->ns))
	{
		flags |= PROPOSAL_PREFER_SUPPLIED;
	}
	this->proposal = this->config->select_proposal(this->config,
												   this->proposals, flags);
	if (!this->proposal)
	{
		DBG1(DBG_IKE, "no acceptable proposal found");
		charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD,
						   this->proposals);
		return FALSE;
	}
	return TRUE;
}

/**
 * Returns the number of CPUs that are configured on the system or only those
 * that are currently online (if we can distinguish this).
 */
static uint32_t get_system_cpus(bool online)
{
	long cpus;

#ifdef WIN32
	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);
	cpus = sysinfo.dwNumberOfProcessors;
#else /* WIN32 */
	cpus = sysconf(online ? _SC_NPROCESSORS_ONLN : _SC_NPROCESSORS_CONF);

	if (cpus < 0)
	{
		DBG1(DBG_IKE, "failed to determine number of CPUs for this system");
		/* assume we have at least one CPU */
		cpus = 1;
	}
#endif /* WIN32 */
	return cpus;
}

/**
 * Finds an unused CPU ID to be assigned to a responder's CHILD_SA, or return
 * CPU_ID_MAX if we don't have a fallback yet.
 */
static uint32_t get_cpu(private_child_create_t *this)
{
	enumerator_t *enumerator;
	hashtable_t *used;
	child_sa_t *child_sa;
	bool found_fallback = FALSE;
	uint32_t cpu;

	used = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 32);

	enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
	while (enumerator->enumerate(enumerator, (void**)&child_sa))
	{
		if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
			this->config->equals(this->config, child_sa->get_config(child_sa)))
		{
			cpu = child_sa->get_cpu(child_sa);
			if (cpu != CPU_ID_MAX)
			{
				used->put(used, (void*)(uintptr_t)cpu, child_sa);
			}
			else
			{
				found_fallback = TRUE;
			}
		}
	}
	enumerator->destroy(enumerator);

	if (!found_fallback)
	{
		cpu = CPU_ID_MAX;
	}
	else
	{
		cpu = get_system_cpus(TRUE) + CPU_ID_MIN - 1;
		for (; cpu > CPU_ID_MIN; cpu--)
		{
			if (!used->get(used, (void*)(uintptr_t)cpu))
			{
				break;
			}
		}
	}
	used->destroy(used);
	return cpu;
}

/**
 * Add a KE payload if a key exchange is used.  As responder we might already
 * have stored the object in the list of completed exchanges.
 */
static bool add_ke_payload(private_child_create_t *this,
						   message_t *message)
{
	key_exchange_t *ke;
	ke_payload_t *pld;

	if (this->ke)
	{
		ke = this->ke;
	}
	else if (!array_get(this->kes, ARRAY_TAIL, &ke))
	{
		return TRUE;
	}

	pld = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, ke);
	if (!pld)
	{
		DBG1(DBG_IKE, "creating KE payload failed");
		return FALSE;
	}
	message->add_payload(message, (payload_t*)pld);
	return TRUE;
}

/**
 * Build payloads in additional exchanges when using multiple key exchanges
 */
static bool build_payloads_multi_ke(private_child_create_t *this,
									message_t *message)
{
	if (!add_ke_payload(this, message))
	{
		return FALSE;
	}
	if (this->link.ptr)
	{
		message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
	}
	return TRUE;
}

/**
 * build the payloads for the message
 */
static bool build_payloads(private_child_create_t *this, message_t *message)
{
	sa_payload_t *sa_payload;
	nonce_payload_t *nonce_payload;
	ts_payload_t *ts_payload;
	kernel_feature_t features;

	if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
	{
		return build_payloads_multi_ke(this, message);
	}

	if (this->initiator)
	{
		sa_payload = sa_payload_create_from_proposals_v2(this->proposals);
	}
	else
	{
		sa_payload = sa_payload_create_from_proposal_v2(this->proposal);
	}
	message->add_payload(message, (payload_t*)sa_payload);

	/* add nonce payload if not in IKE_AUTH */
	if (message->get_exchange_type(message) == CREATE_CHILD_SA)
	{
		nonce_payload = nonce_payload_create(PLV2_NONCE);
		nonce_payload->set_nonce(nonce_payload, this->my_nonce);
		message->add_payload(message, (payload_t*)nonce_payload);
	}

	if (this->link.ptr)
	{
		message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
	}

	if (!add_ke_payload(this, message))
	{
		return FALSE;
	}

	/* add TSi/TSr payloads */
	ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi,
														  this->child.label);
	message->add_payload(message, (payload_t*)ts_payload);
	ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr,
														  this->child.label);
	message->add_payload(message, (payload_t*)ts_payload);

	/* add a notify if we are not in tunnel mode */
	switch (this->mode)
	{
		case MODE_TRANSPORT:
			message->add_notify(message, FALSE, USE_TRANSPORT_MODE, chunk_empty);
			break;
		case MODE_BEET:
			message->add_notify(message, FALSE, USE_BEET_MODE, chunk_empty);
			break;
		case MODE_IPTFS:
		{
			uint8_t iptfs_flags = 0;

			if (!lib->settings->get_bool(lib->settings,
									"%s.iptfs.accept_fragments", TRUE, lib->ns))
			{
				iptfs_flags |= IPTFS_DONT_FRAGMENT;
			}
			message->add_notify(message, FALSE, USE_AGGFRAG,
								chunk_from_thing(iptfs_flags));
			break;
		}
		default:
			break;
	}

	features = charon->kernel->get_features(charon->kernel);
	if (!(features & KERNEL_ESP_V3_TFC))
	{
		message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED,
							chunk_empty);
	}

	if (!this->rekey && this->child.per_cpu)
	{
		chunk_t cpu_id = chunk_empty;
		uint32_t cpu;

		/* always send the notify, but possibly without CPU ID */
		if (this->child.cpu != CPU_ID_MAX)
		{
			cpu = htonl(this->child.cpu);
			cpu_id = chunk_from_thing(cpu);
		}
		message->add_notify(message, FALSE, SA_RESOURCE_INFO,
							cpu_id);
	}
	return TRUE;
}

/**
 * Adds an IPCOMP_SUPPORTED notify to the message, allocating a CPI
 */
static void add_ipcomp_notify(private_child_create_t *this,
								  message_t *message, uint8_t ipcomp)
{
	this->my_cpi = this->child_sa->alloc_cpi(this->child_sa);
	if (this->my_cpi)
	{
		this->ipcomp = ipcomp;
		message->add_notify(message, FALSE, IPCOMP_SUPPORTED,
							chunk_cata("cc", chunk_from_thing(this->my_cpi),
									   chunk_from_thing(ipcomp)));
	}
	else
	{
		DBG1(DBG_IKE, "unable to allocate a CPI from kernel, IPComp disabled");
	}
}

/**
 * handle a received notify payload
 */
static void handle_notify(private_child_create_t *this, notify_payload_t *notify)
{
	chunk_t data;

	switch (notify->get_notify_type(notify))
	{
		case USE_TRANSPORT_MODE:
			this->mode = MODE_TRANSPORT;
			break;
		case USE_BEET_MODE:
			if (this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN))
			{	/* handle private use notify only if we know its meaning */
				this->mode = MODE_BEET;
			}
			else
			{
				DBG1(DBG_IKE, "received a notify strongSwan uses for BEET "
					 "mode, but peer implementation unknown, skipped");
			}
			break;
		case USE_AGGFRAG:
			this->mode = MODE_IPTFS;
			data = notify->get_notification_data(notify);
			this->iptfs_flags = *data.ptr;
			break;
		case IPCOMP_SUPPORTED:
		{
			ipcomp_transform_t ipcomp;
			uint16_t cpi;

			data = notify->get_notification_data(notify);
			cpi = *(uint16_t*)data.ptr;
			ipcomp = (ipcomp_transform_t)(*(data.ptr + 2));
			switch (ipcomp)
			{
				case IPCOMP_DEFLATE:
					this->other_cpi = cpi;
					this->ipcomp_received = ipcomp;
					break;
				case IPCOMP_LZS:
				case IPCOMP_LZJH:
				default:
					DBG1(DBG_IKE, "received IPCOMP_SUPPORTED notify with a "
						 "transform ID we don't support %N",
						 ipcomp_transform_names, ipcomp);
					break;
			}
			break;
		}
		case ESP_TFC_PADDING_NOT_SUPPORTED:
			DBG1(DBG_IKE, "received %N, not using ESPv3 TFC padding",
				 notify_type_names, notify->get_notify_type(notify));
			this->tfcv3 = FALSE;
			break;
		case SA_RESOURCE_INFO:
			chunk_free(&this->resource_info);
			this->resource_info = chunk_clone(notify->get_notification_data(notify));
			this->resource_info_seen = TRUE;
			break;
		default:
			break;
	}
}

/**
 * Collect all key exchanges from the proposal
 */
static void determine_key_exchanges(private_child_create_t *this)
{
	transform_type_t t = KEY_EXCHANGE_METHOD;
	uint16_t alg;
	int i = 1;

	if (!this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
	{	/* no PFS */
		return;
	}

	this->key_exchanges[0].type = t;
	this->key_exchanges[0].method = alg;

	for (t = ADDITIONAL_KEY_EXCHANGE_1; t <= ADDITIONAL_KEY_EXCHANGE_7; t++)
	{
		if (this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
		{
			this->key_exchanges[i].type = t;
			this->key_exchanges[i].method = alg;
			i++;
		}
	}
}

/**
 * Check if additional key exchanges are required
 */
static bool additional_key_exchange_required(private_child_create_t *this)
{
	int i;

	for (i = this->ke_index; i < MAX_KEY_EXCHANGES; i++)
	{
		if (this->key_exchanges[i].type && !this->key_exchanges[i].done)
		{
			return TRUE;
		}
	}
	return FALSE;
}

/**
 * Clear data on key exchanges
 */
static void clear_key_exchanges(private_child_create_t *this)
{
	int i;

	for (i = 0; i < MAX_KEY_EXCHANGES; i++)
	{
		this->key_exchanges[i].type = 0;
		this->key_exchanges[i].method = 0;
		this->key_exchanges[i].done = FALSE;
	}
	this->ke_index = 0;

	array_destroy_offset(this->kes, offsetof(key_exchange_t, destroy));
	this->kes = NULL;
}

/**
 * Process a KE payload
 */
static void process_ke_payload(private_child_create_t *this, ke_payload_t *ke)
{
	key_exchange_method_t method = this->key_exchanges[this->ke_index].method;
	key_exchange_method_t received = ke->get_key_exchange_method(ke);

	/* the proposal is selected after processing the KE payload, so this is
	 * only relevant for additional key exchanges */
	if (method && method != received)
	{
		DBG1(DBG_IKE, "key exchange method in received payload %N doesn't "
			 "match negotiated %N", key_exchange_method_names, received,
			 key_exchange_method_names, method);
		this->ke_failed = TRUE;
		return;
	}

	this->ke_method = received;

	if (!this->initiator)
	{
		DESTROY_IF(this->ke);
		this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
												  received);
		if (!this->ke)
		{
			DBG1(DBG_IKE, "key exchange method %N not supported",
				 key_exchange_method_names, received);
		}
	}
	else if (this->ke)
	{
		if (this->ke->get_method(this->ke) != received)
		{
			DBG1(DBG_IKE, "key exchange method %N in received payload doesn't "
				 "match %N", key_exchange_method_names, received,
				 key_exchange_method_names, this->ke->get_method(this->ke));
			this->ke_failed = TRUE;
		}
	}

	if (this->ke && !this->ke_failed)
	{
		if (!this->ke->set_public_key(this->ke, ke->get_key_exchange_data(ke)))
		{
			DBG1(DBG_IKE, "applying key exchange public key failed");
			this->ke_failed = TRUE;
		}
	}
}

/**
 * Check if the proposed KE method in CREATE_CHILD_SA (received via KE payload)
 * is valid according to the selected proposal.
 */
static bool check_ke_method(private_child_create_t *this, uint16_t *req)
{
	uint16_t alg;

	if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
									   this->ke_method))
	{
		if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
										  &alg, NULL))
		{
			if (req)
			{
				*req = alg;
			}
			return FALSE;
		}
		/* the selected proposal does not use a key exchange method */
		DBG1(DBG_IKE, "ignoring KE payload, agreed on a non-PFS proposal");
		DESTROY_IF(this->ke);
		this->ke = NULL;
		this->ke_method = KE_NONE;
		/* ignore errors that occurred while handling the KE payload */
		this->ke_failed = FALSE;
	}
	return TRUE;
}

/**
 * Check if the proposed key exchange method is valid as responder or whether
 * we should request another KE payload.
 */
static bool check_ke_method_r(private_child_create_t *this, message_t *message)
{
	uint16_t alg;

	if (!check_ke_method(this, &alg))
	{
		DBG1(DBG_IKE, "key exchange method %N unacceptable, requesting %N",
			 key_exchange_method_names, this->ke_method,
			 key_exchange_method_names, alg);
		alg = htons(alg);
		message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
							chunk_from_thing(alg));
		return FALSE;
	}
	else if (this->ke_method != KE_NONE && !this->ke)
	{
		message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
		return FALSE;
	}
	return TRUE;
}

/**
 * Read payloads from message
 */
static void process_payloads(private_child_create_t *this, message_t *message)
{
	enumerator_t *enumerator;
	payload_t *payload;
	sa_payload_t *sa_payload;
	ts_payload_t *ts_payload;

	/* defaults to TUNNEL mode */
	this->mode = MODE_TUNNEL;

	enumerator = message->create_payload_enumerator(message);
	while (enumerator->enumerate(enumerator, &payload))
	{
		switch (payload->get_type(payload))
		{
			case PLV2_SECURITY_ASSOCIATION:
				sa_payload = (sa_payload_t*)payload;
				this->proposals = sa_payload->get_proposals(sa_payload);
				break;
			case PLV2_KEY_EXCHANGE:
				process_ke_payload(this, (ke_payload_t*)payload);
				break;
			case PLV2_TS_INITIATOR:
				ts_payload = (ts_payload_t*)payload;
				this->tsi = ts_payload->get_traffic_selectors(ts_payload);
				this->labels_i = ts_payload->get_sec_labels(ts_payload);
				break;
			case PLV2_TS_RESPONDER:
				ts_payload = (ts_payload_t*)payload;
				this->tsr = ts_payload->get_traffic_selectors(ts_payload);
				this->labels_r = ts_payload->get_sec_labels(ts_payload);
				break;
			case PLV2_NOTIFY:
				handle_notify(this, (notify_payload_t*)payload);
				break;
			default:
				break;
		}
	}
	enumerator->destroy(enumerator);
}

/**
 * Check if we have only the generic label available when using SELinux and not
 * a specific one from an acquire.
 */
static bool generic_label_only(private_child_create_t *this)
{
	return this->config->get_label(this->config) && !this->child.label &&
		   this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX;
}

/**
 * Check if we should defer the creation of this CHILD_SA until after the
 * IKE_SA has been established childless.
 */
static status_t defer_child_sa(private_child_create_t *this)
{
	ike_cfg_t *ike_cfg;
	childless_t policy;

	ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
	policy = ike_cfg->childless(ike_cfg);

	if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_CHILDLESS))
	{
		/* with SELinux, we prefer not to create a CHILD_SA when we only have
		 * the generic label available.  if the peer does not support it,
		 * creating the SA will most likely fail */
		if (policy == CHILDLESS_PREFER ||
			policy == CHILDLESS_FORCE ||
			generic_label_only(this))
		{
			return NEED_MORE;
		}
	}
	else if (policy == CHILDLESS_FORCE)
	{
		DBG1(DBG_IKE, "peer does not support childless IKE_SA initiation");
		return DESTROY_ME;
	}
	return NOT_SUPPORTED;
}

/**
 * Compare the reqids and possibly traffic selectors of two CHILD_SAs for
 * equality.
 *
 * The second CHILD_SA is assumed to be the one newly created.
 */
static bool reqid_and_ts_equals(private_child_create_t *this, child_sa_t *a,
								child_sa_t *b)
{
	/* reqids are allocated based on the traffic selectors. if all the other
	 * selectors are the same, they can only differ if narrowing occurred.
	 *
	 * if the new SA has no reqid assigned, it was initiated manually or due to
	 * a start action and we assume the peer will do the same narrowing it
	 * possibly did before, so we treat the SAs as equal.
	 *
	 * if the new SA has a reqid, it was either triggered by an acquire or
	 * during a reestablishment.  if they are equal, we are done */
	if (!b->get_reqid(b) || a->get_reqid(a) == b->get_reqid(b))
	{
		return TRUE;
	}
	/* if the reqids differ, the one of the established SA was changed due to
	 * narrowing.  in this case, we check if we have either triggering TS or
	 * previous TS.  if so, we check whether the available TS match the TS of
	 * the existing SA.  if they do, there is no point to negotiate another SA.
	 * if not, the peer will potentially narrow the TS to a different set for
	 * the new SA. */
	if (this->packet_tsi && this->packet_tsr)
	{
		return child_sa_ts_match(a, this->packet_tsi, this->packet_tsr);
	}
	if (this->my_ts && this->other_ts)
	{
		return child_sa_ts_lists_match(a, this->my_ts, this->other_ts);
	}
	/* if we don't have any TS to compare, we assume the peer will do the same
	 * narrowing and treat the SAs equal.*/
	return TRUE;
}

/**
 * Compare two CHILD_SA objects for equality.
 *
 * The second CHILD_SA is assumed to be the one newly created.
 */
static bool child_sa_equals(private_child_create_t *this, child_sa_t *a,
							child_sa_t *b)
{
	child_cfg_t *cfg = a->get_config(a);
	return cfg->equals(cfg, b->get_config(b)) &&
		a->get_mark(a, TRUE).value == b->get_mark(b, TRUE).value &&
		a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value &&
		a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) &&
		a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE) &&
		a->get_cpu(a) == b->get_cpu(b) &&
		sec_labels_equal(a->get_label(a), b->get_label(b)) &&
		reqid_and_ts_equals(this, a, b);
}

/**
 * Check if there is a duplicate CHILD_SA already established and we can abort
 * initiating this one.
 */
static bool check_for_duplicate(private_child_create_t *this)
{
	enumerator_t *enumerator;
	child_sa_t *child_sa, *found = NULL;

	enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
	while (enumerator->enumerate(enumerator, (void**)&child_sa))
	{
		if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
			child_sa_equals(this, child_sa, this->child_sa))
		{
			found = child_sa;
			break;
		}
	}
	enumerator->destroy(enumerator);

	if (found)
	{
		linked_list_t *my_ts, *other_ts;

		my_ts = linked_list_create_from_enumerator(
					found->create_ts_enumerator(found, TRUE));
		other_ts = linked_list_create_from_enumerator(
					found->create_ts_enumerator(found, FALSE));

		DBG1(DBG_IKE, "not establishing CHILD_SA %s{%u} due to existing "
			 "duplicate {%u} with SPIs %.8x_i %.8x_o and TS %#R === %#R",
			 this->child_sa->get_name(this->child_sa),
			 this->child_sa->get_unique_id(this->child_sa),
			 found->get_unique_id(found),
			 ntohl(found->get_spi(found, TRUE)),
			 ntohl(found->get_spi(found, FALSE)), my_ts, other_ts);

		my_ts->destroy(my_ts);
		other_ts->destroy(other_ts);
	}
	return found;
}

/**
 * Check if this is an attempt to create an SA with generic label and should
 * be aborted.
 */
static bool check_for_generic_label(private_child_create_t *this)
{
	if (generic_label_only(this))
	{
#if DEBUG_LEVEL >= 1
		sec_label_t *label = this->config->get_label(this->config);
		DBG1(DBG_IKE, "not establishing CHILD_SA %s{%u} with generic "
			 "label '%s'", this->child_sa->get_name(this->child_sa),
			 this->child_sa->get_unique_id(this->child_sa),
			 label->get_string(label));
#endif
		return TRUE;
	}
	return FALSE;
}

METHOD(task_t, build_i_multi_ke, status_t,
	private_child_create_t *this, message_t *message)
{
	key_exchange_method_t method;

	message->set_exchange_type(message, IKE_FOLLOWUP_KE);
	DESTROY_IF(this->ke);
	method = this->key_exchanges[this->ke_index].method;
	this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
											  method);
	if (!this->ke)
	{
		DBG1(DBG_IKE, "negotiated key exchange method %N not supported",
			 key_exchange_method_names, method);
		return FAILED;
	}
	if (!this->link.ptr)
	{
		DBG1(DBG_IKE, "%N notify missing", notify_type_names,
			 ADDITIONAL_KEY_EXCHANGE);
		return FAILED;
	}

	if (!build_payloads_multi_ke(this, message))
	{
		return FAILED;
	}
	return NEED_MORE;
}

/**
 * Prepare proposed traffic selectors as initiator.
 */
static void prepare_proposed_ts(private_child_create_t *this)
{
	enumerator_t *enumerator;
	peer_cfg_t *peer_cfg;
	linked_list_t *list;
	host_t *vip;

	ensure_ts_lists(this);

	list = linked_list_create();
	if (!this->rekey)
	{
		/* check if we want a virtual IP */
		peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
		enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg);
		while (enumerator->enumerate(enumerator, &vip))
		{
			/* propose a 0.0.0.0/0 or ::/0 subnet when we use a virtual IP */
			vip = host_create_any(vip->get_family(vip));
			list->insert_last(list, vip);
		}
		enumerator->destroy(enumerator);
	}
	if (list->get_count(list))
	{
		this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
										list);
		list->destroy_offset(list, offsetof(host_t, destroy));
	}
	else
	{
		list->destroy(list);
		list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
		this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
										list);
		list->destroy(list);
	}
	list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE);
	this->tsr = child_cfg_select_ts(this->config, FALSE, this->other_ts, NULL,
									list);
	list->destroy(list);

	if (this->packet_tsi)
	{
		this->tsi->insert_first(this->tsi,
								this->packet_tsi->clone(this->packet_tsi));
	}
	if (this->packet_tsr)
	{
		this->tsr->insert_first(this->tsr,
								this->packet_tsr->clone(this->packet_tsr));
	}
}

METHOD(task_t, build_i, status_t,
	private_child_create_t *this, message_t *message)
{
	bool no_ke = TRUE;

	switch (message->get_exchange_type(message))
	{
		case IKE_SA_INIT:
			return get_nonce(message, &this->my_nonce);
		case CREATE_CHILD_SA:
			if (!generate_nonce(this))
			{
				message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
				return SUCCESS;
			}
			no_ke = FALSE;
			break;
		case IKE_AUTH:
			switch (defer_child_sa(this))
			{
				case DESTROY_ME:
					/* config mismatch */
					return DESTROY_ME;
				case NEED_MORE:
					/* defer until after IKE_SA has been established */
					chunk_free(&this->my_nonce);
					return NEED_MORE;
				default:
					/* just continue to establish the CHILD_SA */
					break;
			}
			/* send only in the first request, not in subsequent rounds */
			this->public.task.build = (void*)return_need_more;
			break;
		default:
			return NEED_MORE;
	}

	prepare_proposed_ts(this);

	if (!generic_label_only(this) && !this->child.label)
	{	/* in the simple label mode we propose the configured label as we
		 * won't have labels from acquires */
		this->child.label = this->config->get_label(this->config);
		if (this->child.label)
		{
			this->child.label = this->child.label->clone(this->child.label);
		}
	}
	if (this->child.label)
	{
		DBG2(DBG_CFG, "proposing security label '%s'",
			 this->child.label->get_string(this->child.label));
	}
	if (!this->rekey)
	{
		this->child.per_cpu = this->config->has_option(this->config,
													   OPT_PER_CPU_SAS);
	}

	this->proposals = this->config->get_proposals(this->config, no_ke, TRUE);
	this->mode = this->config->get_mode(this->config);

	this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
	this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
	this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
	this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
									 this->ike_sa->get_other_host(this->ike_sa),
									 this->config, &this->child);

	/* check this after creating the object so that its destruction is detected
	 * by controller and trap manager */
	if (!this->rekey &&
		message->get_exchange_type(message) == CREATE_CHILD_SA &&
		(check_for_generic_label(this) || check_for_duplicate(this)))
	{
		message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
		return SUCCESS;
	}

	if (this->child.reqid)
	{
		DBG0(DBG_IKE, "establishing CHILD_SA %s{%u} reqid %d",
			 this->child_sa->get_name(this->child_sa),
			 this->child_sa->get_unique_id(this->child_sa), this->child.reqid);
	}
	else
	{
		DBG0(DBG_IKE, "establishing CHILD_SA %s{%u}",
			 this->child_sa->get_name(this->child_sa),
			 this->child_sa->get_unique_id(this->child_sa));
	}

	if (!allocate_spi(this))
	{
		return FAILED;
	}

	if (no_ke)
	{	/* we might have one set if we are recreating this SA */
		this->ke_method = KE_NONE;
	}
	else if (!this->retry)
	{	/* during a rekeying the method might already be set */
		if (this->ke_method == KE_NONE)
		{
			this->ke_method = this->config->get_algorithm(this->config,
														  KEY_EXCHANGE_METHOD);
		}
	}

	if (!update_and_check_proposals(this))
	{
		DBG1(DBG_IKE, "requested key exchange method %N not contained in any "
			 "of our proposals", key_exchange_method_names, this->ke_method);
		return FAILED;
	}

	if (this->ke_method != KE_NONE)
	{
		this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
												  this->ke_method);
		if (!this->ke)
		{
			DBG1(DBG_IKE, "selected key exchange method %N not supported",
				 key_exchange_method_names, this->ke_method);
			return FAILED;
		}
	}

	if (this->config->has_option(this->config, OPT_IPCOMP))
	{
		/* IPCOMP_DEFLATE is the only transform we support at the moment */
		add_ipcomp_notify(this, message, IPCOMP_DEFLATE);
	}

	if (message->get_exchange_type(message) == IKE_AUTH)
	{
		charon->bus->narrow(charon->bus, this->child_sa,
							NARROW_INITIATOR_PRE_NOAUTH, this->tsi, this->tsr);
	}
	else
	{
		charon->bus->narrow(charon->bus, this->child_sa,
							NARROW_INITIATOR_PRE_AUTH, this->tsi, this->tsr);
	}

	if (!build_payloads(this, message))
	{
		return FAILED;
	}

	this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
	this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
	this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
	this->tsi = NULL;
	this->tsr = NULL;
	this->proposals = NULL;

	return NEED_MORE;
}

/**
 * Process payloads in a IKE_FOLLOWUP_KE message or a CREATE_CHILD_SA response
 */
static void process_link(private_child_create_t *this, message_t *message)
{
	notify_payload_t *notify;
	chunk_t link;

	notify = message->get_notify(message, ADDITIONAL_KEY_EXCHANGE);
	if (notify)
	{
		link = notify->get_notification_data(notify);
		if (this->initiator)
		{
			chunk_free(&this->link);
			this->link = chunk_clone(link);
		}
		else if (!chunk_equals_const(this->link, link))
		{
			DBG1(DBG_IKE, "data in %N notify doesn't match", notify_type_names,
				 ADDITIONAL_KEY_EXCHANGE);
			chunk_free(&this->link);
		}
	}
	else
	{
		chunk_free(&this->link);
	}
}

/**
 * Process payloads in additional exchanges when using multiple key exchanges
 */
static void process_payloads_multi_ke(private_child_create_t *this,
									  message_t *message)
{
	ke_payload_t *ke;

	ke = (ke_payload_t*)message->get_payload(message, PLV2_KEY_EXCHANGE);
	if (ke)
	{
		process_ke_payload(this, ke);
	}
	else
	{
		DBG1(DBG_IKE, "KE payload missing in message");
		this->ke_failed = TRUE;
	}
	process_link(this, message);
}

METHOD(task_t, process_r_multi_ke, status_t,
	private_child_create_t *this, message_t *message)
{
	if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
	{
		process_payloads_multi_ke(this, message);
	}
	return NEED_MORE;
}

METHOD(task_t, process_r, status_t,
	private_child_create_t *this, message_t *message)
{
	switch (message->get_exchange_type(message))
	{
		case IKE_SA_INIT:
			return get_nonce(message, &this->other_nonce);
		case CREATE_CHILD_SA:
			get_nonce(message, &this->other_nonce);
			break;
		case IKE_AUTH:
			/* only handle first AUTH payload, not additional rounds */
			this->public.task.process = (void*)return_need_more;
			break;
		default:
			return NEED_MORE;
	}

	process_payloads(this, message);

	return NEED_MORE;
}

/**
 * handle CHILD_SA setup failure
 */
static void handle_child_sa_failure(private_child_create_t *this,
									message_t *message)
{
	bool is_first;

	if (this->aborted)
	{
		return;
	}

	is_first = message->get_exchange_type(message) == IKE_AUTH;
	if (is_first &&
		lib->settings->get_bool(lib->settings,
								"%s.close_ike_on_child_failure", FALSE, lib->ns))
	{
		/* we delay the delete for 100ms, as the IKE_AUTH response must arrive
		 * first */
		DBG1(DBG_IKE, "closing IKE_SA due CHILD_SA setup failure");
		lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*)
			delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE),
			100);
	}
	else
	{
		DBG1(DBG_IKE, "failed to establish CHILD_SA, keeping IKE_SA");
		charon->bus->alert(charon->bus, ALERT_KEEP_ON_CHILD_SA_FAILURE,
						   is_first);
	}
}

/**
 * Substitute transport mode NAT selectors, if applicable
 */
static linked_list_t* get_ts_if_nat_transport(private_child_create_t *this,
											  bool local, linked_list_t *in)
{
	linked_list_t *out = NULL;
	ike_condition_t cond;

	if (this->mode == MODE_TRANSPORT)
	{
		cond = local ? COND_NAT_HERE : COND_NAT_THERE;
		if (this->ike_sa->has_condition(this->ike_sa, cond))
		{
			out = get_transport_nat_ts(this, local, in);
			if (out->get_count(out) == 0)
			{
				out->destroy(out);
				out = NULL;
			}
		}
	}
	return out;
}

/**
 * Select a matching CHILD config as responder
 */
static child_cfg_t* select_child_cfg(private_child_create_t *this)
{
	peer_cfg_t *peer_cfg;
	child_cfg_t *child_cfg = NULL;

	peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
	if (peer_cfg && this->tsi && this->tsr)
	{
		linked_list_t *listr, *listi, *tsr, *tsi;

		tsr = get_ts_if_nat_transport(this, TRUE, this->tsr);
		tsi = get_ts_if_nat_transport(this, FALSE, this->tsi);

		listr = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
		listi = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE);
		child_cfg = peer_cfg->select_child_cfg(peer_cfg,
									tsr ?: this->tsr, tsi ?: this->tsi,
									listr, listi, this->labels_r, this->labels_i);
		if ((tsi || tsr) && child_cfg &&
			child_cfg->get_mode(child_cfg) != MODE_TRANSPORT)
		{
			/* found a CHILD config, but it doesn't use transport mode */
			child_cfg->destroy(child_cfg);
			child_cfg = NULL;
		}
		if (!child_cfg && (tsi || tsr))
		{
			/* no match for the substituted NAT selectors, try it without */
			child_cfg = peer_cfg->select_child_cfg(peer_cfg,
									this->tsr, this->tsi,
									listr, listi, this->labels_r, this->labels_i);
		}
		listr->destroy(listr);
		listi->destroy(listi);
		DESTROY_OFFSET_IF(tsi, offsetof(traffic_selector_t, destroy));
		DESTROY_OFFSET_IF(tsr, offsetof(traffic_selector_t, destroy));
	}

	return child_cfg;
}

/**
 * Check how to handle a possibly childless IKE_SA
 */
static status_t handle_childless(private_child_create_t *this)
{
	ike_cfg_t *ike_cfg;

	ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);

	if (!this->proposals && !this->tsi && !this->tsr)
	{
		/* looks like a childless IKE_SA, check if we allow it */
		if (ike_cfg->childless(ike_cfg) == CHILDLESS_NEVER)
		{
			/* we don't allow childless initiation */
			DBG1(DBG_IKE, "peer tried to initiate a childless IKE_SA");
			return INVALID_STATE;
		}
		return SUCCESS;
	}

	/* the peer apparently wants to create a regular IKE_SA */
	if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE)
	{
		/* reject it if we only allow childless initiation */
		DBG1(DBG_IKE, "peer did not initiate a childless IKE_SA");
		return INVALID_STATE;
	}
	return NOT_SUPPORTED;
}

/**
 * Select a security label.
 *
 * We already know that the proposed labels match the selected config, just make
 * sure that the proposed/returned labels are the same.
 */
static bool select_label(private_child_create_t *this)
{
	sec_label_t *li, *lr;

	if (!this->config->select_label(this->config, this->labels_i, FALSE, &li, NULL) ||
		!this->config->select_label(this->config, this->labels_r, FALSE, &lr, NULL))
	{	/* sanity check */
		return FALSE;
	}

	if (li)
	{
		if (!li->equals(li, lr))
		{
			DBG1(DBG_CHD, "security labels in TSi and TSr don't match");
			return FALSE;
		}
		else if (!this->child.label)
		{
			this->child.label = li->clone(li);
		}
		else if (!this->child.label->equals(this->child.label, li))
		{
			DBG1(DBG_CHD, "returned security label '%s' doesn't match proposed "
				 "'%s'", li->get_string(li),
				 this->child.label->get_string(this->child.label));
			return FALSE;
		}
	}
	if (this->child.label)
	{
		DBG1(DBG_CFG, "selected security label: %s",
			 this->child.label->get_string(this->child.label));
	}
	return TRUE;
}

/**
 * Handle per-resource information.
 */
static void handle_per_resource(private_child_create_t *this)
{
	if (!this->resource_info_seen)
	{
		DBG1(DBG_IKE, "peer didn't send %N notify, creating regular SA",
			 notify_type_names, SA_RESOURCE_INFO);
		this->child.per_cpu = FALSE;
		this->child.cpu = CPU_ID_MAX;
		return;
	}

	/* as responder, assign a CPU ID (or CPU_ID_MAX if we don't have a
	 * fallback SA yet) */
	if (!this->initiator)
	{
		this->child.per_cpu = TRUE;
		this->child.cpu = get_cpu(this);
	}
	if (this->child.cpu != CPU_ID_MAX && this->resource_info.len)
	{
		DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u, "
			 "peer's ID %+B)", this->child.cpu, &this->resource_info);
	}
	else if (this->child.cpu != CPU_ID_MAX)
	{
		DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u)",
			 this->child.cpu);
	}
	else if (this->resource_info.len)
	{
		DBG1(DBG_IKE, "creating SA as one of several per-resource SAs (peer's "
			 "ID %+B)", &this->resource_info);
	}
	else
	{
		DBG1(DBG_IKE, "creating SA as one of several per-resource SAs");
	}
}

/**
 * Called when a key exchange is done, returns TRUE once all are done.
 */
static bool key_exchange_done(private_child_create_t *this)
{
	bool additional_ke;

	if (!this->ke)
	{
		return TRUE;
	}

	this->key_exchanges[this->ke_index++].done = TRUE;
	additional_ke = additional_key_exchange_required(this);

	array_insert_create(&this->kes, ARRAY_TAIL, this->ke);
	this->ke = NULL;

	return additional_ke ? FALSE : TRUE;
}

/**
 * Complete the current key exchange and install the CHILD_SA if all are done
 * as responder.
 */
static bool key_exchange_done_and_install_r(private_child_create_t *this,
											message_t *message, bool ike_auth)
{
	bool all_done = FALSE;

	if (key_exchange_done(this))
	{
		chunk_clear(&this->link);
		all_done = TRUE;
	}
	else if (!this->link.ptr)
	{
		this->link = chunk_clone(chunk_from_chars(0x42));
	}

	if (!build_payloads(this, message))
	{
		message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
		handle_child_sa_failure(this, message);
		return TRUE;
	}

	if (all_done)
	{
		switch (install_child_sa(this))
		{
			case SUCCESS:
				break;
			case NOT_FOUND:
				message->add_notify(message, FALSE, TS_UNACCEPTABLE,
									chunk_empty);
				handle_child_sa_failure(this, message);
				return TRUE;
			case FAILED:
			default:
				message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
									chunk_empty);
				handle_child_sa_failure(this, message);
				return TRUE;
		}
		if (!this->rekey)
		{	/* invoke the child_up() hook if we are not rekeying */
			charon->bus->child_updown(charon->bus, this->child_sa, TRUE);
		}
	}
	return all_done;
}

METHOD(task_t, build_r_multi_ke, status_t,
	private_child_create_t *this, message_t *message)
{
	if (!this->ke)
	{
		message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}
	if (this->ke_failed)
	{
		message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}
	if (!this->link.ptr)
	{
		DBG1(DBG_IKE, "%N notify missing", notify_type_names,
			 ADDITIONAL_KEY_EXCHANGE);
		message->add_notify(message, FALSE, STATE_NOT_FOUND, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}
	if (!key_exchange_done_and_install_r(this, message, FALSE))
	{
		return NEED_MORE;
	}
	return SUCCESS;
}

METHOD(task_t, build_r, status_t,
	private_child_create_t *this, message_t *message)
{
	payload_t *payload;
	enumerator_t *enumerator;
	bool no_ke = TRUE, ike_auth = FALSE;

	switch (message->get_exchange_type(message))
	{
		case IKE_SA_INIT:
			return get_nonce(message, &this->my_nonce);
		case CREATE_CHILD_SA:
			if (!generate_nonce(this))
			{
				message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
									chunk_empty);
				return SUCCESS;
			}
			no_ke = FALSE;
			break;
		case IKE_AUTH:
			if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
			{	/* wait until all authentication rounds completed */
				return NEED_MORE;
			}
			if (this->ike_sa->has_condition(this->ike_sa, COND_REDIRECTED))
			{	/* no CHILD_SA is created for redirected SAs */
				return SUCCESS;
			}
			switch (handle_childless(this))
			{
				case SUCCESS:
					/* no CHILD_SA built */
					return SUCCESS;
				case INVALID_STATE:
					message->add_notify(message, FALSE, INVALID_SYNTAX,
										chunk_empty);
					return FAILED;
				default:
					/* continue with regular initiation */
					break;
			}
			ike_auth = TRUE;
			break;
		default:
			return NEED_MORE;
	}

	if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING)
	{
		DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA");
		message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
		return SUCCESS;
	}
	if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
	{
		DBG1(DBG_IKE, "unable to create CHILD_SA while deleting IKE_SA");
		message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
		return SUCCESS;
	}

	if (!this->config)
	{
		this->config = select_child_cfg(this);
	}
	if (!this->config || !this->tsi || !this->tsr)
	{
		if (!this->tsi || !this->tsr)
		{
			DBG1(DBG_IKE, "TS payloads missing in message");
		}
		else
		{
			DBG1(DBG_IKE, "traffic selectors %#R === %#R unacceptable",
				 this->tsr, this->tsi);
			charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi,
							   this->tsr);
		}
		message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}

	/* check if ike_config_t task included non-critical error notifies */
	enumerator = message->create_payload_enumerator(message);
	while (enumerator->enumerate(enumerator, &payload))
	{
		if (payload->get_type(payload) == PLV2_NOTIFY)
		{
			notify_payload_t *notify = (notify_payload_t*)payload;

			switch (notify->get_notify_type(notify))
			{
				case INTERNAL_ADDRESS_FAILURE:
				case FAILED_CP_REQUIRED:
				{
					DBG1(DBG_IKE,"configuration payload negotiation "
						 "failed, no CHILD_SA built");
					enumerator->destroy(enumerator);
					handle_child_sa_failure(this, message);
					return SUCCESS;
				}
				default:
					break;
			}
		}
	}
	enumerator->destroy(enumerator);

	if (!select_proposal(this, no_ke))
	{
		message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}

	if (!check_ke_method_r(this, message))
	{	/* the peer will retry, we don't handle this as failure */
		return SUCCESS;
	}

	/* this flag might get reset if the check above notices a proposal without
	 * KE was selected */
	if (this->ke_failed)
	{
		message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}

	determine_key_exchanges(this);

	if (!select_label(this))
	{
		message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}

	this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
	this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
	this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);

	if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
	{
		handle_per_resource(this);
		/* if it's a per-resource SA, we could check if we already have
		 * "too many" and reject this one with TS_MAX_QUEUE, but for now we just
		 * keep it */
	}

	this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
									 this->ike_sa->get_other_host(this->ike_sa),
									 this->config, &this->child);

	this->other_spi = this->proposal->get_spi(this->proposal);
	if (!allocate_spi(this))
	{
		message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
		handle_child_sa_failure(this, message);
		return SUCCESS;
	}
	this->proposal->set_spi(this->proposal, this->my_spi);

	if (this->ipcomp_received != IPCOMP_NONE)
	{
		if (this->config->has_option(this->config, OPT_IPCOMP))
		{
			add_ipcomp_notify(this, message, this->ipcomp_received);
		}
		else
		{
			DBG1(DBG_IKE, "received %N notify but IPComp is disabled, ignoring",
				 notify_type_names, IPCOMP_SUPPORTED);
		}
	}

	switch (narrow_and_check_ts(this, ike_auth))
	{
		case SUCCESS:
			break;
		case NOT_FOUND:
			message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
			handle_child_sa_failure(this, message);
			return SUCCESS;
		case FAILED:
		default:
			message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
			handle_child_sa_failure(this, message);
			return SUCCESS;
	}

	if (!key_exchange_done_and_install_r(this, message, ike_auth))
	{
		this->public.task.build = _build_r_multi_ke;
		this->public.task.process = _process_r_multi_ke;
		return NEED_MORE;
	}
	return SUCCESS;
}

/**
 * Raise alerts for received notify errors
 */
static void raise_alerts(private_child_create_t *this, notify_type_t type)
{
	linked_list_t *list;

	switch (type)
	{
		case NO_PROPOSAL_CHOSEN:
			list = this->config->get_proposals(this->config, FALSE, FALSE);
			charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD, list);
			list->destroy_offset(list, offsetof(proposal_t, destroy));
			break;
		case TS_UNACCEPTABLE:
			charon->bus->alert(charon->bus, ALERT_TS_MISMATCH,
							   this->tsi, this->tsr);
			break;
		default:
			break;
	}
}

METHOD(task_t, build_i_delete, status_t,
	private_child_create_t *this, message_t *message)
{
	message->set_exchange_type(message, INFORMATIONAL);
	if (this->my_spi && this->proto)
	{
		delete_payload_t *del;

		del = delete_payload_create(PLV2_DELETE, this->proto);
		del->add_spi(del, this->my_spi);
		message->add_payload(message, (payload_t*)del);

		DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x",
			 protocol_id_names, this->proto, ntohl(this->my_spi));
	}
	return SUCCESS;
}

/**
 * Change task to delete the failed CHILD_SA as initiator
 */
static status_t delete_failed_sa(private_child_create_t *this)
{
	if (this->my_spi && this->proto)
	{
		this->public.task.build = _build_i_delete;
		/* destroying it here allows the rekey task to differentiate between
		 * this and the multi-KE case */
		this->child_sa->destroy(this->child_sa);
		this->child_sa = NULL;
		return NEED_MORE;
	}
	return SUCCESS;
}

/**
 * Complete the current key exchange and install the CHILD_SA if all are done
 * as initiator.
 */
static status_t key_exchange_done_and_install_i(private_child_create_t *this,
												message_t *message, bool ike_auth)
{
	if (key_exchange_done(this))
	{
		if (install_child_sa(this) == SUCCESS)
		{
			if (!this->rekey)
			{	/* invoke the child_up() hook if we are not rekeying */
				charon->bus->child_updown(charon->bus, this->child_sa,
										  TRUE);
			}
			return SUCCESS;
		}
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}
	return NEED_MORE;
}

METHOD(task_t, process_i_multi_ke, status_t,
	private_child_create_t *this, message_t *message)
{
	if (message->get_notify(message, TEMPORARY_FAILURE))
	{
		DBG1(DBG_IKE, "received %N notify", notify_type_names,
			 TEMPORARY_FAILURE);
		if (!this->rekey && !this->aborted)
		{	/* the rekey task will retry itself if necessary */
			schedule_delayed_retry(this);
		}
		return SUCCESS;
	}

	process_payloads_multi_ke(this, message);

	if (this->ke_failed || this->aborted)
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	return key_exchange_done_and_install_i(this, message, FALSE);
}

METHOD(task_t, process_i, status_t,
	private_child_create_t *this, message_t *message)
{
	enumerator_t *enumerator;
	payload_t *payload;
	bool no_ke = TRUE, ike_auth = FALSE;

	switch (message->get_exchange_type(message))
	{
		case IKE_SA_INIT:
			return get_nonce(message, &this->other_nonce);
		case CREATE_CHILD_SA:
			get_nonce(message, &this->other_nonce);
			no_ke = FALSE;
			break;
		case IKE_AUTH:
			if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
			{	/* wait until all authentication rounds completed */
				return NEED_MORE;
			}
			if (defer_child_sa(this) == NEED_MORE)
			{	/* defer until after IKE_SA has been established */
				chunk_free(&this->other_nonce);
				return NEED_MORE;
			}
			ike_auth = TRUE;
			break;
		default:
			return NEED_MORE;
	}

	/* check for erroneous notifies */
	enumerator = message->create_payload_enumerator(message);
	while (enumerator->enumerate(enumerator, &payload))
	{
		if (payload->get_type(payload) == PLV2_NOTIFY)
		{
			notify_payload_t *notify = (notify_payload_t*)payload;
			notify_type_t type = notify->get_notify_type(notify);

			switch (type)
			{
				/* handle notify errors related to CHILD_SA only */
				case NO_PROPOSAL_CHOSEN:
				case SINGLE_PAIR_REQUIRED:
				case NO_ADDITIONAL_SAS:
				case INTERNAL_ADDRESS_FAILURE:
				case FAILED_CP_REQUIRED:
				case TS_UNACCEPTABLE:
				case INVALID_SELECTORS:
				{
					DBG1(DBG_IKE, "received %N notify, no CHILD_SA built",
						 notify_type_names, type);
					enumerator->destroy(enumerator);
					raise_alerts(this, type);
					handle_child_sa_failure(this, message);
					/* an error in CHILD_SA creation is not critical */
					return SUCCESS;
				}
				case TEMPORARY_FAILURE:
				{
					DBG1(DBG_IKE, "received %N notify", notify_type_names, type);
					enumerator->destroy(enumerator);
					if (!this->rekey && !this->aborted)
					{	/* the rekey task will retry itself if necessary */
						schedule_delayed_retry(this);
					}
					return SUCCESS;
				}
				case INVALID_KE_PAYLOAD:
				{
					chunk_t data;
					uint16_t alg = KE_NONE;

					if (this->aborted)
					{	/* nothing to do if the task was aborted */
						DBG1(DBG_IKE, "received %N notify in aborted %N task",
							 notify_type_names, type, task_type_names,
							 TASK_CHILD_CREATE);
						enumerator->destroy(enumerator);
						return SUCCESS;
					}
					data = notify->get_notification_data(notify);
					if (data.len == sizeof(alg))
					{
						alg = untoh16(data.ptr);
					}
					if (this->retry)
					{
						DBG1(DBG_IKE, "already retried with key exchange method "
							 "%N, ignore requested %N", key_exchange_method_names,
							 this->ke_method, key_exchange_method_names, alg);
						handle_child_sa_failure(this, message);
						/* an error in CHILD_SA creation is not critical */
						return SUCCESS;
					}
					DBG1(DBG_IKE, "peer didn't accept key exchange method %N, "
						 "it requested %N", key_exchange_method_names,
						 this->ke_method, key_exchange_method_names, alg);
					this->retry = TRUE;
					this->ke_method = alg;
					this->child_sa->set_state(this->child_sa, CHILD_RETRYING);
					this->public.task.migrate(&this->public.task, this->ike_sa);
					enumerator->destroy(enumerator);
					return NEED_MORE;
				}
				default:
				{
					if (message->get_exchange_type(message) == CREATE_CHILD_SA)
					{	/* handle notifies if not handled in IKE_AUTH */
						if (type <= 16383)
						{
							DBG1(DBG_IKE, "received %N notify error",
								 notify_type_names, type);
							enumerator->destroy(enumerator);
							return SUCCESS;
						}
						DBG2(DBG_IKE, "received %N notify",
							 notify_type_names, type);
					}
					break;
				}
			}
		}
	}
	enumerator->destroy(enumerator);

	process_payloads(this, message);

	if (!this->tsi || !this->tsr)
	{
		DBG1(DBG_IKE, "TS payloads missing in message");
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	if (!select_proposal(this, no_ke))
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	this->other_spi = this->proposal->get_spi(this->proposal);
	this->proposal->set_spi(this->proposal, this->my_spi);

	if (this->aborted)
	{
		DBG1(DBG_IKE, "deleting CHILD_SA %s{%u} with SPIs %.8x_i %.8x_o of "
			 "aborted %N task",
			 this->child_sa->get_name(this->child_sa),
			 this->child_sa->get_unique_id(this->child_sa),
			 ntohl(this->my_spi), ntohl(this->other_spi),
			 task_type_names, TASK_CHILD_CREATE);
		return delete_failed_sa(this);
	}

	if (this->ipcomp == IPCOMP_NONE && this->ipcomp_received != IPCOMP_NONE)
	{
		DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify without requesting"
			 " one, no CHILD_SA built");
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}
	else if (this->ipcomp != IPCOMP_NONE && this->ipcomp_received == IPCOMP_NONE)
	{
		DBG1(DBG_IKE, "peer didn't accept our proposed IPComp transforms, "
			 "IPComp is disabled");
		this->ipcomp = IPCOMP_NONE;
	}
	else if (this->ipcomp != IPCOMP_NONE && this->ipcomp != this->ipcomp_received)
	{
		DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify we didn't propose, "
			 "no CHILD_SA built");
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	if (!check_ke_method(this, NULL))
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	if (this->ke_failed)
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	determine_key_exchanges(this);

	if (!select_label(this))
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	if (narrow_and_check_ts(this, ike_auth) != SUCCESS)
	{
		handle_child_sa_failure(this, message);
		return delete_failed_sa(this);
	}

	if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
	{
		handle_per_resource(this);
		/* the peer might not have returned the notify, update the flag */
		this->child_sa->set_per_cpu(this->child_sa, this->child.per_cpu);
	}

	if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
	{
		/* if the installation failed, we delete the failed SA, i.e. build() was
		 * changed, otherwise, we switch to multi-KE mode */
		if (this->public.task.build == _build_i)
		{
			/* if we don't have the notify, we handle it in build() */
			process_link(this, message);
			this->public.task.build = _build_i_multi_ke;
			this->public.task.process = _process_i_multi_ke;
		}
		return NEED_MORE;
	}
	return SUCCESS;
}

METHOD(child_create_t, use_reqid, void,
	private_child_create_t *this, uint32_t reqid)
{
	uint32_t existing_reqid = this->child.reqid;

	if (!reqid || charon->kernel->ref_reqid(charon->kernel, reqid) == SUCCESS)
	{
		this->child.reqid = reqid;
		if (existing_reqid)
		{
			charon->kernel->release_reqid(charon->kernel, existing_reqid);
		}
	}
}

METHOD(child_create_t, use_marks, void,
	private_child_create_t *this, uint32_t in, uint32_t out)
{
	this->child.mark_in = in;
	this->child.mark_out = out;
}

METHOD(child_create_t, use_if_ids, void,
	private_child_create_t *this, uint32_t in, uint32_t out)
{
	this->child.if_id_in = in;
	this->child.if_id_out = out;
}

METHOD(child_create_t, use_label, void,
	private_child_create_t *this, sec_label_t *label)
{
	DESTROY_IF(this->child.label);
	this->child.label = label ? label->clone(label) : NULL;
}

METHOD(child_create_t, use_per_cpu, void,
	private_child_create_t *this, bool per_cpu, uint32_t cpu)
{
	this->child.per_cpu = per_cpu ? per_cpu : cpu != CPU_ID_MAX;
	this->child.cpu = cpu;
}

/**
 * Prepare traffic selectors for reuse when recreating a CHILD_SA.
 */
static void reuse_ts(private_child_create_t *this, bool local, child_sa_t *old,
					 traffic_selector_list_t **target)
{
	enumerator_t *old_ts, *hosts_enum;
	linked_list_t *hosts, *list;
	traffic_selector_t *ts, *new_ts;
	host_t *host;

	old_ts = old->create_ts_enumerator(old, local);
	if (this->rekey)
	{
		/* when rekeying, we just reuse the previous TS. this is also the only
		 * way a responder reuses TS */
		*target = traffic_selector_list_create_from_enumerator(old_ts);
		return;
	}

	/* when recreating/reauthenticating, we check whether the dynamic IPs of
	 * the IKE_SA (as copied from the old SA) match the TS and replace
	 * them with dynamic TS (reusing protocol/ports in case of narrowing) so
	 * they get updated to possibly new IPs when the TS are prepared later */
	list = linked_list_create();
	hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);
	hosts_enum = hosts->create_enumerator(hosts);
	while (old_ts->enumerate(old_ts, &ts))
	{
		new_ts = NULL;
		while (hosts_enum->enumerate(hosts_enum, &host))
		{
			if (ts->is_host(ts, host))
			{
				new_ts = traffic_selector_create_dynamic(ts->get_protocol(ts),
														 ts->get_from_port(ts),
														 ts->get_to_port(ts));
				break;
			}
		}
		hosts->reset_enumerator(hosts, hosts_enum);

		if (!new_ts)
		{
			new_ts = ts->clone(ts);
		}
		list->insert_last(list, new_ts);
	}
	hosts_enum->destroy(hosts_enum);
	hosts->destroy(hosts);
	old_ts->destroy(old_ts);

	*target = traffic_selector_list_create_from_list(list);
}

METHOD(child_create_t, recreate_sa, void,
	private_child_create_t *this, child_sa_t *old)
{
	if (this->initiator)
	{
		proposal_t *proposal;
		uint16_t ke_method;

		proposal = old->get_proposal(old);
		if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD,
									&ke_method, NULL))
		{
			/* reuse the KE method negotiated previously */
			this->ke_method = ke_method;
		}
	}

	/* use previously negotiated traffic selectors */
	reuse_ts(this, TRUE, old, &this->my_ts);
	reuse_ts(this, FALSE, old, &this->other_ts);
}

METHOD(child_create_t, get_child, child_sa_t*,
	private_child_create_t *this)
{
	return this->child_sa;
}

METHOD(child_create_t, get_other_spi, uint32_t,
	private_child_create_t *this)
{
	return this->other_spi;
}

METHOD(child_create_t, set_config, void,
	private_child_create_t *this, child_cfg_t *cfg)
{
	DESTROY_IF(this->config);
	this->config = cfg;
}

METHOD(child_create_t, get_config, child_cfg_t*,
	private_child_create_t *this)
{
	return this->initiator ? this->config : NULL;
}

METHOD(child_create_t, get_lower_nonce, chunk_t,
	private_child_create_t *this)
{
	if (chunk_compare_prefix(this->my_nonce, this->other_nonce) < 0)
	{
		return this->my_nonce;
	}
	else
	{
		return this->other_nonce;
	}
}

METHOD(child_create_t, abort_, void,
	private_child_create_t *this)
{
	this->aborted = TRUE;
}

METHOD(task_t, get_type, task_type_t,
	private_child_create_t *this)
{
	return TASK_CHILD_CREATE;
}

METHOD(task_t, migrate, void,
	private_child_create_t *this, ike_sa_t *ike_sa)
{
	chunk_free(&this->my_nonce);
	chunk_free(&this->other_nonce);
	chunk_free(&this->link);
	chunk_free(&this->resource_info);
	DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
	DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
	DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
	DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
	DESTROY_IF(this->child_sa);
	DESTROY_IF(this->proposal);
	DESTROY_IF(this->nonceg);
	DESTROY_IF(this->ke);
	this->ke_failed = FALSE;
	clear_key_exchanges(this);
	DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
	if (!this->rekey && !this->retry)
	{
		this->ke_method = KE_NONE;
	}
	this->ike_sa = ike_sa;
	this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
	this->proposal = NULL;
	this->proposals = NULL;
	this->tsi = NULL;
	this->tsr = NULL;
	this->labels_i = NULL;
	this->labels_r = NULL;
	this->ke = NULL;
	this->nonceg = NULL;
	this->child_sa = NULL;
	this->mode = MODE_TUNNEL;
	this->ipcomp = IPCOMP_NONE;
	this->ipcomp_received = IPCOMP_NONE;
	this->other_cpi = 0;
	this->established = FALSE;
	this->resource_info_seen = FALSE;
	this->public.task.build = _build_i;
	this->public.task.process = _process_i;
}

METHOD(task_t, destroy, void,
	private_child_create_t *this)
{
	chunk_free(&this->my_nonce);
	chunk_free(&this->other_nonce);
	chunk_free(&this->link);
	chunk_free(&this->resource_info);
	DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
	DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
	DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
	DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
	if (!this->established)
	{
		DESTROY_IF(this->child_sa);
	}
	if (this->child.reqid)
	{
		charon->kernel->release_reqid(charon->kernel, this->child.reqid);
	}
	DESTROY_IF(this->packet_tsi);
	DESTROY_IF(this->packet_tsr);
	DESTROY_IF(this->my_ts);
	DESTROY_IF(this->other_ts);
	DESTROY_IF(this->proposal);
	DESTROY_IF(this->ke);
	clear_key_exchanges(this);
	DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
	DESTROY_IF(this->config);
	DESTROY_IF(this->nonceg);
	DESTROY_IF(this->child.label);
	free(this);
}

/*
 * Described in header.
 */
child_create_t *child_create_create(ike_sa_t *ike_sa,
									child_cfg_t *config, bool rekey,
									traffic_selector_t *tsi,
									traffic_selector_t *tsr, uint32_t seq)
{
	private_child_create_t *this;

	INIT(this,
		.public = {
			.get_child = _get_child,
			.get_other_spi = _get_other_spi,
			.set_config = _set_config,
			.get_config = _get_config,
			.get_lower_nonce = _get_lower_nonce,
			.use_reqid = _use_reqid,
			.use_marks = _use_marks,
			.use_if_ids = _use_if_ids,
			.use_label = _use_label,
			.use_per_cpu = _use_per_cpu,
			.recreate_sa = _recreate_sa,
			.abort = _abort_,
			.task = {
				.get_type = _get_type,
				.migrate = _migrate,
				.destroy = _destroy,
			},
		},
		.child = {
			.cpu = CPU_ID_MAX,
			.seq = seq,
		},
		.ike_sa = ike_sa,
		.config = config,
		.packet_tsi = tsi ? tsi->clone(tsi) : NULL,
		.packet_tsr = tsr ? tsr->clone(tsr) : NULL,
		.ke_method = KE_NONE,
		.keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
		.mode = MODE_TUNNEL,
		.tfcv3 = TRUE,
		.ipcomp = IPCOMP_NONE,
		.ipcomp_received = IPCOMP_NONE,
		.rekey = rekey,
		.retry = FALSE,
	);

	if (config)
	{
		this->public.task.build = _build_i;
		this->public.task.process = _process_i;
		this->initiator = TRUE;
	}
	else
	{
		this->public.task.build = _build_r;
		this->public.task.process = _process_r;
		this->initiator = FALSE;
	}
	return &this->public;
}
