| /* |
| * Copyright (C) 2011-2019 Tobias Brunner |
| * Copyright (C) 2007-2011 Martin Willi |
| * Copyright (C) 2011 revosec AG |
| * HSR Hochschule fuer Technik Rapperswil |
| * |
| * 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 "controller.h" |
| |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| |
| #include <daemon.h> |
| #include <library.h> |
| #include <threading/thread.h> |
| #include <threading/spinlock.h> |
| #include <threading/semaphore.h> |
| |
| typedef struct private_controller_t private_controller_t; |
| typedef struct interface_listener_t interface_listener_t; |
| typedef struct interface_logger_t interface_logger_t; |
| |
| /** |
| * Private data of an stroke_t object. |
| */ |
| struct private_controller_t { |
| |
| /** |
| * Public part of stroke_t object. |
| */ |
| controller_t public; |
| }; |
| |
| /** |
| * helper struct for the logger interface |
| */ |
| struct interface_logger_t { |
| /** |
| * public logger interface |
| */ |
| logger_t public; |
| |
| /** |
| * reference to the listener |
| */ |
| interface_listener_t *listener; |
| |
| /** |
| * interface callback (listener gets redirected to here) |
| */ |
| controller_cb_t callback; |
| |
| /** |
| * user parameter to pass to callback |
| */ |
| void *param; |
| }; |
| |
| /** |
| * helper struct to map listener callbacks to interface callbacks |
| */ |
| struct interface_listener_t { |
| |
| /** |
| * public bus listener interface |
| */ |
| listener_t public; |
| |
| /** |
| * logger interface |
| */ |
| interface_logger_t logger; |
| |
| /** |
| * status of the operation, return to method callers |
| */ |
| status_t status; |
| |
| /** |
| * child configuration, used for initiate |
| */ |
| child_cfg_t *child_cfg; |
| |
| /** |
| * peer configuration, used for initiate |
| */ |
| peer_cfg_t *peer_cfg; |
| |
| /** |
| * IKE_SA to handle |
| */ |
| ike_sa_t *ike_sa; |
| |
| /** |
| * unique ID, used for various methods |
| */ |
| uint32_t id; |
| |
| /** |
| * semaphore to implement wait_for_listener() |
| */ |
| semaphore_t *done; |
| |
| /** |
| * spinlock to update the IKE_SA handle properly |
| */ |
| spinlock_t *lock; |
| |
| union { |
| /** |
| * whether to check limits during initiation |
| */ |
| bool limits; |
| |
| /** |
| * whether to force termination |
| */ |
| bool force; |
| } options; |
| }; |
| |
| |
| typedef struct interface_job_t interface_job_t; |
| |
| /** |
| * job for asynchronous listen operations |
| */ |
| struct interface_job_t { |
| |
| /** |
| * job interface |
| */ |
| job_t public; |
| |
| /** |
| * associated listener |
| */ |
| interface_listener_t listener; |
| |
| /** |
| * the job is reference counted as the thread executing a job as well as |
| * the thread waiting in wait_for_listener() require it but either of them |
| * could be done first |
| */ |
| refcount_t refcount; |
| }; |
| |
| /** |
| * This function wakes a thread that is waiting in wait_for_listener(), |
| * either from a listener or from a job. |
| */ |
| static inline bool listener_done(interface_listener_t *listener) |
| { |
| if (listener->done) |
| { |
| listener->done->post(listener->done); |
| } |
| return FALSE; |
| } |
| |
| /** |
| * thread_cleanup_t handler to unregister a listener. |
| */ |
| static void listener_unregister(interface_listener_t *listener) |
| { |
| charon->bus->remove_listener(charon->bus, &listener->public); |
| charon->bus->remove_logger(charon->bus, &listener->logger.public); |
| } |
| |
| /** |
| * Registers the listener, executes the job and then waits synchronously until |
| * the listener is done or the timeout occurred. |
| * |
| * @note Use 'return listener_done(listener)' to properly unregister a listener |
| * |
| * @param listener listener to register |
| * @param job job to execute asynchronously when registered, or NULL |
| * @param timeout max timeout in ms to listen for events, 0 to disable |
| * @return TRUE if timed out |
| */ |
| static bool wait_for_listener(interface_job_t *job, u_int timeout) |
| { |
| interface_listener_t *listener = &job->listener; |
| bool old, timed_out = FALSE; |
| |
| /* avoid that the job is destroyed too early */ |
| ref_get(&job->refcount); |
| |
| listener->done = semaphore_create(0); |
| |
| charon->bus->add_logger(charon->bus, &listener->logger.public); |
| charon->bus->add_listener(charon->bus, &listener->public); |
| lib->processor->queue_job(lib->processor, &job->public); |
| |
| thread_cleanup_push((thread_cleanup_t)listener_unregister, listener); |
| old = thread_cancelability(TRUE); |
| if (timeout) |
| { |
| timed_out = listener->done->timed_wait(listener->done, timeout); |
| } |
| else |
| { |
| listener->done->wait(listener->done); |
| } |
| thread_cancelability(old); |
| thread_cleanup_pop(TRUE); |
| return timed_out; |
| } |
| |
| METHOD(logger_t, listener_log, void, |
| interface_logger_t *this, debug_t group, level_t level, int thread, |
| ike_sa_t *ike_sa, const char *message) |
| { |
| ike_sa_t *target; |
| |
| this->listener->lock->lock(this->listener->lock); |
| target = this->listener->ike_sa; |
| this->listener->lock->unlock(this->listener->lock); |
| |
| if (target == ike_sa) |
| { |
| if (!this->callback(this->param, group, level, ike_sa, message)) |
| { |
| this->listener->status = NEED_MORE; |
| listener_done(this->listener); |
| } |
| } |
| } |
| |
| METHOD(logger_t, listener_get_level, level_t, |
| interface_logger_t *this, debug_t group) |
| { |
| /* in order to allow callback listeners to decide what they want to log |
| * we request any log message, but only if we actually want logging */ |
| return this->callback == controller_cb_empty ? LEVEL_SILENT : LEVEL_PRIVATE; |
| } |
| |
| METHOD(job_t, get_priority_medium, job_priority_t, |
| job_t *this) |
| { |
| return JOB_PRIO_MEDIUM; |
| } |
| |
| METHOD(listener_t, ike_state_change, bool, |
| interface_listener_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) |
| { |
| ike_sa_t *target; |
| |
| this->lock->lock(this->lock); |
| target = this->ike_sa; |
| this->lock->unlock(this->lock); |
| |
| if (target == ike_sa) |
| { |
| switch (state) |
| { |
| case IKE_ESTABLISHED: |
| { |
| #ifdef ME |
| peer_cfg_t *peer_cfg = ike_sa->get_peer_cfg(ike_sa); |
| #endif /* ME */ |
| /* we're done if we didn't initiate a CHILD_SA */ |
| if (!this->child_cfg |
| #ifdef ME |
| /* the same is always true for mediation connections */ |
| || peer_cfg->is_mediation(peer_cfg) |
| #endif /* ME */ |
| ) |
| { |
| this->status = SUCCESS; |
| return listener_done(this); |
| } |
| break; |
| } |
| case IKE_DESTROYING: |
| return listener_done(this); |
| default: |
| break; |
| } |
| } |
| return TRUE; |
| } |
| |
| METHOD(listener_t, ike_state_change_terminate, bool, |
| interface_listener_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) |
| { |
| ike_sa_t *target; |
| |
| this->lock->lock(this->lock); |
| target = this->ike_sa; |
| this->lock->unlock(this->lock); |
| |
| if (target == ike_sa) |
| { |
| switch (state) |
| { |
| case IKE_DESTROYING: |
| this->status = SUCCESS; |
| return listener_done(this); |
| default: |
| break; |
| } |
| } |
| return TRUE; |
| } |
| |
| METHOD(listener_t, child_state_change, bool, |
| interface_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, |
| child_sa_state_t state) |
| { |
| ike_sa_t *target; |
| |
| this->lock->lock(this->lock); |
| target = this->ike_sa; |
| this->lock->unlock(this->lock); |
| |
| if (target == ike_sa) |
| { |
| switch (state) |
| { |
| case CHILD_INSTALLED: |
| this->status = SUCCESS; |
| return listener_done(this); |
| case CHILD_DESTROYING: |
| switch (child_sa->get_state(child_sa)) |
| { |
| case CHILD_RETRYING: |
| /* retrying with a different DH group; survive another |
| * initiation round */ |
| this->status = NEED_MORE; |
| return TRUE; |
| case CHILD_CREATED: |
| if (this->status == NEED_MORE) |
| { |
| this->status = FAILED; |
| return TRUE; |
| } |
| break; |
| default: |
| break; |
| } |
| return listener_done(this); |
| default: |
| break; |
| } |
| } |
| return TRUE; |
| } |
| |
| METHOD(listener_t, child_state_change_terminate, bool, |
| interface_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, |
| child_sa_state_t state) |
| { |
| ike_sa_t *target; |
| |
| this->lock->lock(this->lock); |
| target = this->ike_sa; |
| this->lock->unlock(this->lock); |
| |
| if (target == ike_sa) |
| { |
| switch (state) |
| { |
| case CHILD_DESTROYING: |
| switch (child_sa->get_state(child_sa)) |
| { |
| case CHILD_DELETED: |
| /* proper delete */ |
| this->status = SUCCESS; |
| break; |
| default: |
| break; |
| } |
| return listener_done(this); |
| default: |
| break; |
| } |
| } |
| return TRUE; |
| } |
| |
| METHOD(job_t, destroy_job, void, |
| interface_job_t *this) |
| { |
| if (ref_put(&this->refcount)) |
| { |
| this->listener.lock->destroy(this->listener.lock); |
| DESTROY_IF(this->listener.done); |
| free(this); |
| } |
| } |
| |
| METHOD(controller_t, create_ike_sa_enumerator, enumerator_t*, |
| private_controller_t *this, bool wait) |
| { |
| return charon->ike_sa_manager->create_enumerator(charon->ike_sa_manager, |
| wait); |
| } |
| |
| METHOD(job_t, initiate_execute, job_requeue_t, |
| interface_job_t *job) |
| { |
| ike_sa_t *ike_sa; |
| interface_listener_t *listener = &job->listener; |
| peer_cfg_t *peer_cfg = listener->peer_cfg; |
| |
| ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, |
| peer_cfg); |
| peer_cfg->destroy(peer_cfg); |
| if (!ike_sa) |
| { |
| DESTROY_IF(listener->child_cfg); |
| listener->status = FAILED; |
| listener_done(listener); |
| return JOB_REQUEUE_NONE; |
| } |
| listener->lock->lock(listener->lock); |
| listener->ike_sa = ike_sa; |
| listener->lock->unlock(listener->lock); |
| |
| |
| if (listener->options.limits && ike_sa->get_state(ike_sa) == IKE_CREATED) |
| { /* only check if we are not reusing an IKE_SA */ |
| u_int half_open, limit_half_open, limit_job_load; |
| |
| half_open = charon->ike_sa_manager->get_half_open_count( |
| charon->ike_sa_manager, NULL, FALSE); |
| limit_half_open = lib->settings->get_int(lib->settings, |
| "%s.init_limit_half_open", 0, lib->ns); |
| limit_job_load = lib->settings->get_int(lib->settings, |
| "%s.init_limit_job_load", 0, lib->ns); |
| if (limit_half_open && half_open >= limit_half_open) |
| { |
| DBG1(DBG_IKE, "abort IKE_SA initiation, half open IKE_SA count of " |
| "%d exceeds limit of %d", half_open, limit_half_open); |
| charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, |
| ike_sa); |
| DESTROY_IF(listener->child_cfg); |
| listener->status = INVALID_STATE; |
| listener_done(listener); |
| return JOB_REQUEUE_NONE; |
| } |
| if (limit_job_load) |
| { |
| u_int jobs = 0, i; |
| |
| for (i = 0; i < JOB_PRIO_MAX; i++) |
| { |
| jobs += lib->processor->get_job_load(lib->processor, i); |
| } |
| if (jobs > limit_job_load) |
| { |
| DBG1(DBG_IKE, "abort IKE_SA initiation, job load of %d exceeds " |
| "limit of %d", jobs, limit_job_load); |
| charon->ike_sa_manager->checkin_and_destroy( |
| charon->ike_sa_manager, ike_sa); |
| DESTROY_IF(listener->child_cfg); |
| listener->status = INVALID_STATE; |
| listener_done(listener); |
| return JOB_REQUEUE_NONE; |
| } |
| } |
| } |
| |
| if (ike_sa->initiate(ike_sa, listener->child_cfg, 0, NULL, NULL) == SUCCESS) |
| { |
| if (!listener->logger.callback) |
| { |
| listener->status = SUCCESS; |
| } |
| charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); |
| } |
| else |
| { |
| listener->status = FAILED; |
| charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, |
| ike_sa); |
| } |
| return JOB_REQUEUE_NONE; |
| } |
| |
| METHOD(controller_t, initiate, status_t, |
| private_controller_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg, |
| controller_cb_t callback, void *param, u_int timeout, bool limits) |
| { |
| interface_job_t *job; |
| status_t status; |
| |
| INIT(job, |
| .listener = { |
| .public = { |
| .ike_state_change = _ike_state_change, |
| .child_state_change = _child_state_change, |
| }, |
| .logger = { |
| .public = { |
| .log = _listener_log, |
| .get_level = _listener_get_level, |
| }, |
| .callback = callback, |
| .param = param, |
| }, |
| .status = FAILED, |
| .child_cfg = child_cfg, |
| .peer_cfg = peer_cfg, |
| .lock = spinlock_create(), |
| .options.limits = limits, |
| }, |
| .public = { |
| .execute = _initiate_execute, |
| .get_priority = _get_priority_medium, |
| .destroy = _destroy_job, |
| }, |
| .refcount = 1, |
| ); |
| job->listener.logger.listener = &job->listener; |
| thread_cleanup_push((void*)destroy_job, job); |
| |
| if (callback == NULL) |
| { |
| initiate_execute(job); |
| } |
| else |
| { |
| if (wait_for_listener(job, timeout)) |
| { |
| job->listener.status = OUT_OF_RES; |
| } |
| } |
| status = job->listener.status; |
| thread_cleanup_pop(TRUE); |
| return status; |
| } |
| |
| METHOD(job_t, terminate_ike_execute, job_requeue_t, |
| interface_job_t *job) |
| { |
| interface_listener_t *listener = &job->listener; |
| uint32_t unique_id = listener->id; |
| ike_sa_t *ike_sa; |
| |
| ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager, |
| unique_id); |
| if (!ike_sa) |
| { |
| DBG1(DBG_IKE, "unable to terminate IKE_SA: ID %d not found", unique_id); |
| listener->status = NOT_FOUND; |
| /* release listener */ |
| listener_done(listener); |
| return JOB_REQUEUE_NONE; |
| } |
| listener->lock->lock(listener->lock); |
| listener->ike_sa = ike_sa; |
| listener->lock->unlock(listener->lock); |
| |
| if (!listener->logger.callback) |
| { /* if we don't wait for the result, either outcome below is a success */ |
| listener->status = SUCCESS; |
| } |
| |
| if (ike_sa->delete(ike_sa, listener->options.force) != DESTROY_ME) |
| { /* delete queued */ |
| charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); |
| } |
| else |
| { |
| charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, |
| ike_sa); |
| } |
| return JOB_REQUEUE_NONE; |
| } |
| |
| METHOD(controller_t, terminate_ike, status_t, |
| controller_t *this, uint32_t unique_id, bool force, |
| controller_cb_t callback, void *param, u_int timeout) |
| { |
| interface_job_t *job; |
| status_t status; |
| |
| INIT(job, |
| .listener = { |
| .public = { |
| .ike_state_change = _ike_state_change_terminate, |
| }, |
| .logger = { |
| .public = { |
| .log = _listener_log, |
| .get_level = _listener_get_level, |
| }, |
| .callback = callback, |
| .param = param, |
| }, |
| .status = FAILED, |
| .id = unique_id, |
| .lock = spinlock_create(), |
| }, |
| .public = { |
| .execute = _terminate_ike_execute, |
| .get_priority = _get_priority_medium, |
| .destroy = _destroy_job, |
| }, |
| .refcount = 1, |
| ); |
| job->listener.logger.listener = &job->listener; |
| thread_cleanup_push((void*)destroy_job, job); |
| |
| if (callback == NULL) |
| { |
| job->listener.options.force = force; |
| terminate_ike_execute(job); |
| } |
| else |
| { |
| if (!timeout) |
| { |
| job->listener.options.force = force; |
| } |
| if (wait_for_listener(job, timeout)) |
| { |
| job->listener.status = OUT_OF_RES; |
| |
| if (force) |
| { /* force termination once timeout is reached */ |
| job->listener.options.force = TRUE; |
| terminate_ike_execute(job); |
| } |
| } |
| } |
| status = job->listener.status; |
| thread_cleanup_pop(TRUE); |
| return status; |
| } |
| |
| METHOD(job_t, terminate_child_execute, job_requeue_t, |
| interface_job_t *job) |
| { |
| interface_listener_t *listener = &job->listener; |
| uint32_t id = listener->id; |
| child_sa_t *child_sa; |
| ike_sa_t *ike_sa; |
| |
| ike_sa = charon->child_sa_manager->checkout_by_id(charon->child_sa_manager, |
| id, &child_sa); |
| if (!ike_sa) |
| { |
| DBG1(DBG_IKE, "unable to terminate, CHILD_SA with ID %d not found", id); |
| listener->status = NOT_FOUND; |
| /* release listener */ |
| listener_done(listener); |
| return JOB_REQUEUE_NONE; |
| } |
| listener->lock->lock(listener->lock); |
| listener->ike_sa = ike_sa; |
| listener->lock->unlock(listener->lock); |
| |
| if (ike_sa->delete_child_sa(ike_sa, child_sa->get_protocol(child_sa), |
| child_sa->get_spi(child_sa, TRUE), FALSE) != DESTROY_ME) |
| { |
| if (!listener->logger.callback) |
| { |
| listener->status = SUCCESS; |
| } |
| charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); |
| } |
| else |
| { |
| listener->status = FAILED; |
| charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, |
| ike_sa); |
| } |
| return JOB_REQUEUE_NONE; |
| } |
| |
| METHOD(controller_t, terminate_child, status_t, |
| controller_t *this, uint32_t unique_id, |
| controller_cb_t callback, void *param, u_int timeout) |
| { |
| interface_job_t *job; |
| status_t status; |
| |
| INIT(job, |
| .listener = { |
| .public = { |
| .ike_state_change = _ike_state_change_terminate, |
| .child_state_change = _child_state_change_terminate, |
| }, |
| .logger = { |
| .public = { |
| .log = _listener_log, |
| .get_level = _listener_get_level, |
| }, |
| .callback = callback, |
| .param = param, |
| }, |
| .status = FAILED, |
| .id = unique_id, |
| .lock = spinlock_create(), |
| }, |
| .public = { |
| .execute = _terminate_child_execute, |
| .get_priority = _get_priority_medium, |
| .destroy = _destroy_job, |
| }, |
| .refcount = 1, |
| ); |
| job->listener.logger.listener = &job->listener; |
| thread_cleanup_push((void*)destroy_job, job); |
| |
| if (callback == NULL) |
| { |
| terminate_child_execute(job); |
| } |
| else |
| { |
| if (wait_for_listener(job, timeout)) |
| { |
| job->listener.status = OUT_OF_RES; |
| } |
| } |
| status = job->listener.status; |
| thread_cleanup_pop(TRUE); |
| return status; |
| } |
| |
| /** |
| * See header |
| */ |
| bool controller_cb_empty(void *param, debug_t group, level_t level, |
| ike_sa_t *ike_sa, const char *message) |
| { |
| return TRUE; |
| } |
| |
| METHOD(controller_t, destroy, void, |
| private_controller_t *this) |
| { |
| free(this); |
| } |
| |
| /* |
| * Described in header-file |
| */ |
| controller_t *controller_create(void) |
| { |
| private_controller_t *this; |
| |
| INIT(this, |
| .public = { |
| .create_ike_sa_enumerator = _create_ike_sa_enumerator, |
| .initiate = _initiate, |
| .terminate_ike = _terminate_ike, |
| .terminate_child = _terminate_child, |
| .destroy = _destroy, |
| }, |
| ); |
| |
| return &this->public; |
| } |